From f897c8d94698d4f3d80aa588f45fe67e31479275 Mon Sep 17 00:00:00 2001 From: John Davis Date: Fri, 15 Dec 2023 19:27:40 -0500 Subject: [PATCH] Convert all classes w/methods into functions Although this would work, we'd need to add the staticmethod decorator to make it right (and fix mypy). Functions, I think, are simpler and cleaner here. --- lib/galaxy/webapps/galaxy/api/authenticate.py | 21 +- lib/galaxy/webapps/galaxy/api/cloud.py | 121 +-- .../webapps/galaxy/api/configuration.py | 177 ++-- .../webapps/galaxy/api/dataset_collections.py | 189 +++-- lib/galaxy/webapps/galaxy/api/datatypes.py | 201 ++--- .../galaxy/api/display_applications.py | 62 +- lib/galaxy/webapps/galaxy/api/drs.py | 128 +-- .../webapps/galaxy/api/folder_contents.py | 108 +-- lib/galaxy/webapps/galaxy/api/folders.py | 220 ++--- lib/galaxy/webapps/galaxy/api/forms.py | 34 +- lib/galaxy/webapps/galaxy/api/genomes.py | 118 +-- lib/galaxy/webapps/galaxy/api/group_roles.py | 100 +-- lib/galaxy/webapps/galaxy/api/group_users.py | 152 ++-- lib/galaxy/webapps/galaxy/api/groups.py | 157 ++-- lib/galaxy/webapps/galaxy/api/help.py | 27 +- lib/galaxy/webapps/galaxy/api/job_tokens.py | 49 +- lib/galaxy/webapps/galaxy/api/libraries.py | 319 +++---- lib/galaxy/webapps/galaxy/api/licenses.py | 34 +- lib/galaxy/webapps/galaxy/api/metrics.py | 23 +- .../webapps/galaxy/api/notifications.py | 414 ++++----- lib/galaxy/webapps/galaxy/api/object_store.py | 52 +- lib/galaxy/webapps/galaxy/api/pages.py | 421 +++++----- lib/galaxy/webapps/galaxy/api/quotas.py | 243 +++--- lib/galaxy/webapps/galaxy/api/remote_files.py | 115 +-- lib/galaxy/webapps/galaxy/api/roles.py | 114 +-- .../webapps/galaxy/api/storage_cleaner.py | 200 ++--- lib/galaxy/webapps/galaxy/api/tags.py | 41 +- lib/galaxy/webapps/galaxy/api/tasks.py | 15 +- lib/galaxy/webapps/galaxy/api/tool_data.py | 197 ++--- lib/galaxy/webapps/galaxy/api/tours.py | 31 +- lib/tool_shed/webapp/api2/authenticate.py | 23 +- lib/tool_shed/webapp/api2/categories.py | 128 +-- lib/tool_shed/webapp/api2/configuration.py | 19 +- lib/tool_shed/webapp/api2/repositories.py | 793 +++++++++--------- lib/tool_shed/webapp/api2/tools.py | 147 ++-- lib/tool_shed/webapp/api2/users.py | 361 ++++---- 36 files changed, 2831 insertions(+), 2723 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/authenticate.py b/lib/galaxy/webapps/galaxy/api/authenticate.py index 4bc198325622..4d05d7f38dee 100644 --- a/lib/galaxy/webapps/galaxy/api/authenticate.py +++ b/lib/galaxy/webapps/galaxy/api/authenticate.py @@ -48,14 +48,13 @@ def options(self, trans: GalaxyWebTransaction, **kwd): # trans.response.headers['Access-Control-Allow-Methods'] = 'POST, PUT, GET, OPTIONS, DELETE' -class FastAPIAuthenticate: - @router.get( - "/api/authenticate/baseauth", - summary="Returns returns an API key for authenticated user based on BaseAuth headers.", - ) - def get_api_key( - request: Request, authentication_service: AuthenticationService = depends(AuthenticationService) - ) -> APIKeyResponse: - authorization = request.headers.get("Authorization") - auth = {"HTTP_AUTHORIZATION": authorization} - return authentication_service.get_api_key(auth, request) +@router.get( + "/api/authenticate/baseauth", + summary="Returns returns an API key for authenticated user based on BaseAuth headers.", +) +def get_api_key( + request: Request, authentication_service: AuthenticationService = depends(AuthenticationService) +) -> APIKeyResponse: + authorization = request.headers.get("Authorization") + auth = {"HTTP_AUTHORIZATION": authorization} + return authentication_service.get_api_key(auth, request) diff --git a/lib/galaxy/webapps/galaxy/api/cloud.py b/lib/galaxy/webapps/galaxy/api/cloud.py index d95b83604721..8b38322b752a 100644 --- a/lib/galaxy/webapps/galaxy/api/cloud.py +++ b/lib/galaxy/webapps/galaxy/api/cloud.py @@ -32,68 +32,69 @@ router = Router(tags=["cloud"]) -class FastAPICloudController: - @router.get( - "/api/cloud/storage", - summary="Lists cloud-based buckets (e.g., S3 bucket, Azure blob) user has defined. Is not yet implemented", - deprecated=True, - ) - def index( - response: Response, - ): - # TODO: This can be implemented leveraging PluggedMedia objects (part of the user-based object store project) - response.status_code = status.HTTP_501_NOT_IMPLEMENTED - return StatusCode(detail="Not yet implemented.", status=501) +@router.get( + "/api/cloud/storage", + summary="Lists cloud-based buckets (e.g., S3 bucket, Azure blob) user has defined. Is not yet implemented", + deprecated=True, +) +def index( + response: Response, +): + # TODO: This can be implemented leveraging PluggedMedia objects (part of the user-based object store project) + response.status_code = status.HTTP_501_NOT_IMPLEMENTED + return StatusCode(detail="Not yet implemented.", status=501) - @router.post( - "/api/cloud/storage/get", - summary="Gets given objects from a given cloud-based bucket to a Galaxy history.", - deprecated=True, - ) - def get( - payload: CloudObjects = Body(default=Required), - trans: ProvidesHistoryContext = DependsOnTrans, - cloud_manager: CloudManager = depends(CloudManager), - datasets_serializer: DatasetSerializer = depends(DatasetSerializer), - ) -> DatasetSummaryList: - datasets = cloud_manager.get( - trans=trans, - history_id=payload.history_id, - bucket_name=payload.bucket, - objects=payload.objects, - authz_id=payload.authz_id, - input_args=payload.input_args, - ) - rtv = [] - for dataset in datasets: - rtv.append(datasets_serializer.serialize_to_view(dataset, view="summary")) - return DatasetSummaryList.construct(__root__=rtv) - @router.post( - "/api/cloud/storage/send", - summary="Sends given dataset(s) in a given history to a given cloud-based bucket.", - deprecated=True, +@router.post( + "/api/cloud/storage/get", + summary="Gets given objects from a given cloud-based bucket to a Galaxy history.", + deprecated=True, +) +def get( + payload: CloudObjects = Body(default=Required), + trans: ProvidesHistoryContext = DependsOnTrans, + cloud_manager: CloudManager = depends(CloudManager), + datasets_serializer: DatasetSerializer = depends(DatasetSerializer), +) -> DatasetSummaryList: + datasets = cloud_manager.get( + trans=trans, + history_id=payload.history_id, + bucket_name=payload.bucket, + objects=payload.objects, + authz_id=payload.authz_id, + input_args=payload.input_args, ) - def send( - payload: CloudDatasets = Body(default=Required), - trans: ProvidesHistoryContext = DependsOnTrans, - cloud_manager: CloudManager = depends(CloudManager), - ) -> CloudDatasetsResponse: - log.info( - msg="Received api/send request for `{}` datasets using authnz with id `{}`, and history `{}`." - "".format( - "all the dataset in the given history" if not payload.dataset_ids else len(payload.dataset_ids), - payload.authz_id, - payload.history_id, - ) - ) + rtv = [] + for dataset in datasets: + rtv.append(datasets_serializer.serialize_to_view(dataset, view="summary")) + return DatasetSummaryList.construct(__root__=rtv) - sent, failed = cloud_manager.send( - trans=trans, - history_id=payload.history_id, - bucket_name=payload.bucket, - authz_id=payload.authz_id, - dataset_ids=payload.dataset_ids, - overwrite_existing=payload.overwrite_existing, + +@router.post( + "/api/cloud/storage/send", + summary="Sends given dataset(s) in a given history to a given cloud-based bucket.", + deprecated=True, +) +def send( + payload: CloudDatasets = Body(default=Required), + trans: ProvidesHistoryContext = DependsOnTrans, + cloud_manager: CloudManager = depends(CloudManager), +) -> CloudDatasetsResponse: + log.info( + msg="Received api/send request for `{}` datasets using authnz with id `{}`, and history `{}`." + "".format( + "all the dataset in the given history" if not payload.dataset_ids else len(payload.dataset_ids), + payload.authz_id, + payload.history_id, ) - return CloudDatasetsResponse(sent_dataset_labels=sent, failed_dataset_labels=failed, bucket_name=payload.bucket) + ) + + sent, failed = cloud_manager.send( + trans=trans, + history_id=payload.history_id, + bucket_name=payload.bucket, + authz_id=payload.authz_id, + dataset_ids=payload.dataset_ids, + overwrite_existing=payload.overwrite_existing, + ) + return CloudDatasetsResponse(sent_dataset_labels=sent, failed_dataset_labels=failed, bucket_name=payload.bucket) diff --git a/lib/galaxy/webapps/galaxy/api/configuration.py b/lib/galaxy/webapps/galaxy/api/configuration.py index 72a06bb4f7b2..7082d5c5d774 100644 --- a/lib/galaxy/webapps/galaxy/api/configuration.py +++ b/lib/galaxy/webapps/galaxy/api/configuration.py @@ -39,92 +39,97 @@ ) -class FastAPIConfiguration: - @router.get( - "/api/whoami", - summary="Return information about the current authenticated user", - response_description="Information about the current authenticated user", - ) - def whoami(trans: ProvidesUserContext = DependsOnTrans) -> Optional[UserModel]: - """Return information about the current authenticated user.""" - return _user_to_model(trans.user) - - @router.get( - "/api/configuration", - summary="Return an object containing exposable configuration settings", - response_description="Object containing exposable configuration settings", - ) - def index( - trans: ProvidesUserContext = DependsOnTrans, - view: Optional[str] = SerializationViewQueryParam, - keys: Optional[str] = SerializationKeysQueryParam, - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ) -> Dict[str, Any]: - """ - Return an object containing exposable configuration settings. - - A more complete list is returned if the user is an admin. - Pass in `view` and a comma-seperated list of keys to control which - configuration settings are returned. - """ - return _index(configuration_manager, trans, view, keys) - - @router.get( - "/api/version", - summary="Return Galaxy version information: major/minor version, optional extra info", - response_description="Galaxy version information: major/minor version, optional extra info", - ) - def version( - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ) -> Dict[str, Any]: - """Return Galaxy version information: major/minor version, optional extra info.""" - return configuration_manager.version() - - @router.get( - "/api/configuration/dynamic_tool_confs", - require_admin=True, - summary="Return dynamic tool configuration files", - response_description="Dynamic tool configuration files", - ) - def dynamic_tool_confs( - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ) -> List[Dict[str, str]]: - """Return dynamic tool configuration files.""" - return configuration_manager.dynamic_tool_confs() - - @router.get( - "/api/configuration/decode/{encoded_id}", - require_admin=True, - summary="Decode a given id", - response_description="Decoded id", - ) - def decode_id( - encoded_id: str = EncodedIdPathParam, - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ) -> Dict[str, int]: - """Decode a given id.""" - return configuration_manager.decode_id(encoded_id) - - @router.get( - "/api/configuration/tool_lineages", - require_admin=True, - summary="Return tool lineages for tools that have them", - response_description="Tool lineages for tools that have them", - ) - def tool_lineages( - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ) -> List[Dict[str, Dict]]: - """Return tool lineages for tools that have them.""" - return configuration_manager.tool_lineages() - - @router.put( - "/api/configuration/toolbox", require_admin=True, summary="Reload the Galaxy toolbox (but not individual tools)" - ) - def reload_toolbox( - configuration_manager: ConfigurationManager = depends(ConfigurationManager), - ): - """Reload the Galaxy toolbox (but not individual tools).""" - configuration_manager.reload_toolbox() +@router.get( + "/api/whoami", + summary="Return information about the current authenticated user", + response_description="Information about the current authenticated user", +) +def whoami(trans: ProvidesUserContext = DependsOnTrans) -> Optional[UserModel]: + """Return information about the current authenticated user.""" + return _user_to_model(trans.user) + + +@router.get( + "/api/configuration", + summary="Return an object containing exposable configuration settings", + response_description="Object containing exposable configuration settings", +) +def index( + trans: ProvidesUserContext = DependsOnTrans, + view: Optional[str] = SerializationViewQueryParam, + keys: Optional[str] = SerializationKeysQueryParam, + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +) -> Dict[str, Any]: + """ + Return an object containing exposable configuration settings. + + A more complete list is returned if the user is an admin. + Pass in `view` and a comma-seperated list of keys to control which + configuration settings are returned. + """ + return _index(configuration_manager, trans, view, keys) + + +@router.get( + "/api/version", + summary="Return Galaxy version information: major/minor version, optional extra info", + response_description="Galaxy version information: major/minor version, optional extra info", +) +def version( + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +) -> Dict[str, Any]: + """Return Galaxy version information: major/minor version, optional extra info.""" + return configuration_manager.version() + + +@router.get( + "/api/configuration/dynamic_tool_confs", + require_admin=True, + summary="Return dynamic tool configuration files", + response_description="Dynamic tool configuration files", +) +def dynamic_tool_confs( + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +) -> List[Dict[str, str]]: + """Return dynamic tool configuration files.""" + return configuration_manager.dynamic_tool_confs() + + +@router.get( + "/api/configuration/decode/{encoded_id}", + require_admin=True, + summary="Decode a given id", + response_description="Decoded id", +) +def decode_id( + encoded_id: str = EncodedIdPathParam, + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +) -> Dict[str, int]: + """Decode a given id.""" + return configuration_manager.decode_id(encoded_id) + + +@router.get( + "/api/configuration/tool_lineages", + require_admin=True, + summary="Return tool lineages for tools that have them", + response_description="Tool lineages for tools that have them", +) +def tool_lineages( + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +) -> List[Dict[str, Dict]]: + """Return tool lineages for tools that have them.""" + return configuration_manager.tool_lineages() + + +@router.put( + "/api/configuration/toolbox", require_admin=True, summary="Reload the Galaxy toolbox (but not individual tools)" +) +def reload_toolbox( + configuration_manager: ConfigurationManager = depends(ConfigurationManager), +): + """Reload the Galaxy toolbox (but not individual tools).""" + configuration_manager.reload_toolbox() def _user_to_model(user): diff --git a/lib/galaxy/webapps/galaxy/api/dataset_collections.py b/lib/galaxy/webapps/galaxy/api/dataset_collections.py index c8a2045355b8..647f468d28ad 100644 --- a/lib/galaxy/webapps/galaxy/api/dataset_collections.py +++ b/lib/galaxy/webapps/galaxy/api/dataset_collections.py @@ -47,95 +47,100 @@ ) -class FastAPIDatasetCollections: - @router.post( - "/api/dataset_collections", - summary="Create a new dataset collection instance.", - ) - def create( - trans: ProvidesHistoryContext = DependsOnTrans, - payload: CreateNewCollectionPayload = Body(...), - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> HDCADetailed: - return service.create(trans, payload) - - @router.post( - "/api/dataset_collections/{id}/copy", - summary="Copy the given collection datasets to a new collection using a new `dbkey` attribute.", - ) - def copy( - trans: ProvidesHistoryContext = DependsOnTrans, - id: DecodedDatabaseIdField = Path(..., description="The ID of the dataset collection to copy."), - payload: UpdateCollectionAttributePayload = Body(...), - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ): - service.copy(trans, id, payload) - - @router.get( - "/api/dataset_collections/{id}/attributes", - summary="Returns `dbkey`/`extension` attributes for all the collection elements.", - ) - def attributes( - trans: ProvidesHistoryContext = DependsOnTrans, - id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, - instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> DatasetCollectionAttributesResult: - return service.attributes(trans, id, instance_type) - - @router.get( - "/api/dataset_collections/{id}/suitable_converters", - summary="Returns a list of applicable converters for all datatypes in the given collection.", - ) - def suitable_converters( - trans: ProvidesHistoryContext = DependsOnTrans, - id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, - instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> SuitableConverters: - return service.suitable_converters(trans, id, instance_type) - - @router.get( - "/api/dataset_collections/{id}", - summary="Returns detailed information about the given collection.", - ) - def show( - trans: ProvidesHistoryContext = DependsOnTrans, - id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, - instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> HDCADetailed: - return service.show(trans, id, instance_type) - - @router.get( - "/api/dataset_collections/{hdca_id}/contents/{parent_id}", - name="contents_dataset_collection", - summary="Returns direct child contents of indicated dataset collection parent ID.", - ) - def contents( - trans: ProvidesHistoryContext = DependsOnTrans, - hdca_id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, - parent_id: DecodedDatabaseIdField = Path( - ..., - description="Parent collection ID describing what collection the contents belongs to.", - ), - instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, - limit: Optional[int] = Query( - default=None, - description="The maximum number of content elements to return.", - ), - offset: Optional[int] = Query( - default=None, - description="The number of content elements that will be skipped before returning.", - ), - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> DatasetCollectionContentElements: - return service.contents(trans, hdca_id, parent_id, instance_type, limit, offset) - - @router.get("/api/dataset_collection_element/{dce_id}") - def content( - trans: ProvidesHistoryContext = DependsOnTrans, - dce_id: DecodedDatabaseIdField = DatasetCollectionElementIdPathParam, - service: DatasetCollectionsService = depends(DatasetCollectionsService), - ) -> DCESummary: - return service.dce_content(trans, dce_id) +@router.post( + "/api/dataset_collections", + summary="Create a new dataset collection instance.", +) +def create( + trans: ProvidesHistoryContext = DependsOnTrans, + payload: CreateNewCollectionPayload = Body(...), + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> HDCADetailed: + return service.create(trans, payload) + + +@router.post( + "/api/dataset_collections/{id}/copy", + summary="Copy the given collection datasets to a new collection using a new `dbkey` attribute.", +) +def copy( + trans: ProvidesHistoryContext = DependsOnTrans, + id: DecodedDatabaseIdField = Path(..., description="The ID of the dataset collection to copy."), + payload: UpdateCollectionAttributePayload = Body(...), + service: DatasetCollectionsService = depends(DatasetCollectionsService), +): + service.copy(trans, id, payload) + + +@router.get( + "/api/dataset_collections/{id}/attributes", + summary="Returns `dbkey`/`extension` attributes for all the collection elements.", +) +def attributes( + trans: ProvidesHistoryContext = DependsOnTrans, + id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, + instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> DatasetCollectionAttributesResult: + return service.attributes(trans, id, instance_type) + + +@router.get( + "/api/dataset_collections/{id}/suitable_converters", + summary="Returns a list of applicable converters for all datatypes in the given collection.", +) +def suitable_converters( + trans: ProvidesHistoryContext = DependsOnTrans, + id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, + instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> SuitableConverters: + return service.suitable_converters(trans, id, instance_type) + + +@router.get( + "/api/dataset_collections/{id}", + summary="Returns detailed information about the given collection.", +) +def show( + trans: ProvidesHistoryContext = DependsOnTrans, + id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, + instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> HDCADetailed: + return service.show(trans, id, instance_type) + + +@router.get( + "/api/dataset_collections/{hdca_id}/contents/{parent_id}", + name="contents_dataset_collection", + summary="Returns direct child contents of indicated dataset collection parent ID.", +) +def contents( + trans: ProvidesHistoryContext = DependsOnTrans, + hdca_id: DecodedDatabaseIdField = DatasetCollectionIdPathParam, + parent_id: DecodedDatabaseIdField = Path( + ..., + description="Parent collection ID describing what collection the contents belongs to.", + ), + instance_type: DatasetCollectionInstanceType = InstanceTypeQueryParam, + limit: Optional[int] = Query( + default=None, + description="The maximum number of content elements to return.", + ), + offset: Optional[int] = Query( + default=None, + description="The number of content elements that will be skipped before returning.", + ), + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> DatasetCollectionContentElements: + return service.contents(trans, hdca_id, parent_id, instance_type, limit, offset) + + +@router.get("/api/dataset_collection_element/{dce_id}") +def content( + trans: ProvidesHistoryContext = DependsOnTrans, + dce_id: DecodedDatabaseIdField = DatasetCollectionElementIdPathParam, + service: DatasetCollectionsService = depends(DatasetCollectionsService), +) -> DCESummary: + return service.dce_content(trans, dce_id) diff --git a/lib/galaxy/webapps/galaxy/api/datatypes.py b/lib/galaxy/webapps/galaxy/api/datatypes.py index 2bf00ed30352..000e50bfd5ee 100644 --- a/lib/galaxy/webapps/galaxy/api/datatypes.py +++ b/lib/galaxy/webapps/galaxy/api/datatypes.py @@ -54,101 +54,108 @@ ) -class FastAPIDatatypes: - @router.get( - "/api/datatypes", - summary="Lists all available data types", - response_description="List of data types", - ) - async def index( - extension_only: Optional[bool] = ExtensionOnlyQueryParam, - upload_only: Optional[bool] = UploadOnlyQueryParam, - datatypes_registry: Registry = depends(Registry), - ) -> Union[List[DatatypeDetails], List[str]]: - """Gets the list of all available data types.""" - return view_index(datatypes_registry, extension_only, upload_only) - - @router.get( - "/api/datatypes/mapping", - summary="Returns mappings for data types and their implementing classes", - response_description="Dictionary to map data types with their classes", - ) - async def mapping(datatypes_registry: Registry = depends(Registry)) -> DatatypesMap: - """Gets mappings for data types.""" - return view_mapping(datatypes_registry) - - @router.get( - "/api/datatypes/types_and_mapping", - summary="Returns all the data types extensions and their mappings", - response_description="Dictionary to map data types with their classes", - ) - async def types_and_mapping( - extension_only: Optional[bool] = ExtensionOnlyQueryParam, - upload_only: Optional[bool] = UploadOnlyQueryParam, - datatypes_registry: Registry = depends(Registry), - ) -> DatatypesCombinedMap: - """Combines the datatype information from (/api/datatypes) and the - mapping information from (/api/datatypes/mapping) into a single - response.""" - return DatatypesCombinedMap( - datatypes=view_index(datatypes_registry, extension_only, upload_only), - datatypes_mapping=view_mapping(datatypes_registry), - ) - - @router.get( - "/api/datatypes/sniffers", - summary="Returns the list of all installed sniffers", - response_description="List of datatype sniffers", - ) - async def sniffers(datatypes_registry: Registry = depends(Registry)) -> List[str]: - """Gets the list of all installed data type sniffers.""" - return view_sniffers(datatypes_registry) - - @router.get( - "/api/datatypes/converters", - summary="Returns the list of all installed converters", - response_description="List of all datatype converters", - ) - async def converters(datatypes_registry: Registry = depends(Registry)) -> DatatypeConverterList: - """Gets the list of all installed converters.""" - return view_converters(datatypes_registry) - - @router.get( - "/api/datatypes/edam_formats", - summary="Returns a dictionary/map of datatypes and EDAM formats", - response_description="Dictionary/map of datatypes and EDAM formats", - ) - async def edam_formats(datatypes_registry: Registry = depends(Registry)) -> Dict[str, str]: - """Gets a map of datatypes and their corresponding EDAM formats.""" - return cast(Dict[str, str], view_edam_formats(datatypes_registry)) - - @router.get( - "/api/datatypes/edam_formats/detailed", - summary="Returns a dictionary of datatypes and EDAM format details", - response_description="Dictionary of EDAM format details containing the EDAM iri, label, and definition", - response_model=DatatypesEDAMDetailsDict, - ) - async def edam_formats_detailed(datatypes_registry: Registry = depends(Registry)): - """Gets a map of datatypes and their corresponding EDAM formats. - EDAM formats contain the EDAM iri, label, and definition.""" - return view_edam_formats(datatypes_registry, True) - - @router.get( - "/api/datatypes/edam_data", - summary="Returns a dictionary/map of datatypes and EDAM data", - response_description="Dictionary/map of datatypes and EDAM data", - ) - async def edam_data(datatypes_registry: Registry = depends(Registry)) -> Dict[str, str]: - """Gets a map of datatypes and their corresponding EDAM data.""" - return cast(Dict[str, str], view_edam_data(datatypes_registry)) - - @router.get( - "/api/datatypes/edam_data/detailed", - summary="Returns a dictionary of datatypes and EDAM data details", - response_description="Dictionary of EDAM data details containing the EDAM iri, label, and definition", - response_model=DatatypesEDAMDetailsDict, +@router.get( + "/api/datatypes", + summary="Lists all available data types", + response_description="List of data types", +) +async def index( + extension_only: Optional[bool] = ExtensionOnlyQueryParam, + upload_only: Optional[bool] = UploadOnlyQueryParam, + datatypes_registry: Registry = depends(Registry), +) -> Union[List[DatatypeDetails], List[str]]: + """Gets the list of all available data types.""" + return view_index(datatypes_registry, extension_only, upload_only) + + +@router.get( + "/api/datatypes/mapping", + summary="Returns mappings for data types and their implementing classes", + response_description="Dictionary to map data types with their classes", +) +async def mapping(datatypes_registry: Registry = depends(Registry)) -> DatatypesMap: + """Gets mappings for data types.""" + return view_mapping(datatypes_registry) + + +@router.get( + "/api/datatypes/types_and_mapping", + summary="Returns all the data types extensions and their mappings", + response_description="Dictionary to map data types with their classes", +) +async def types_and_mapping( + extension_only: Optional[bool] = ExtensionOnlyQueryParam, + upload_only: Optional[bool] = UploadOnlyQueryParam, + datatypes_registry: Registry = depends(Registry), +) -> DatatypesCombinedMap: + """Combines the datatype information from (/api/datatypes) and the + mapping information from (/api/datatypes/mapping) into a single + response.""" + return DatatypesCombinedMap( + datatypes=view_index(datatypes_registry, extension_only, upload_only), + datatypes_mapping=view_mapping(datatypes_registry), ) - async def edam_data_detailed(datatypes_registry: Registry = depends(Registry)): - """Gets a map of datatypes and their corresponding EDAM data. - EDAM data contains the EDAM iri, label, and definition.""" - return view_edam_data(datatypes_registry, True) + + +@router.get( + "/api/datatypes/sniffers", + summary="Returns the list of all installed sniffers", + response_description="List of datatype sniffers", +) +async def sniffers(datatypes_registry: Registry = depends(Registry)) -> List[str]: + """Gets the list of all installed data type sniffers.""" + return view_sniffers(datatypes_registry) + + +@router.get( + "/api/datatypes/converters", + summary="Returns the list of all installed converters", + response_description="List of all datatype converters", +) +async def converters(datatypes_registry: Registry = depends(Registry)) -> DatatypeConverterList: + """Gets the list of all installed converters.""" + return view_converters(datatypes_registry) + + +@router.get( + "/api/datatypes/edam_formats", + summary="Returns a dictionary/map of datatypes and EDAM formats", + response_description="Dictionary/map of datatypes and EDAM formats", +) +async def edam_formats(datatypes_registry: Registry = depends(Registry)) -> Dict[str, str]: + """Gets a map of datatypes and their corresponding EDAM formats.""" + return cast(Dict[str, str], view_edam_formats(datatypes_registry)) + + +@router.get( + "/api/datatypes/edam_formats/detailed", + summary="Returns a dictionary of datatypes and EDAM format details", + response_description="Dictionary of EDAM format details containing the EDAM iri, label, and definition", + response_model=DatatypesEDAMDetailsDict, +) +async def edam_formats_detailed(datatypes_registry: Registry = depends(Registry)): + """Gets a map of datatypes and their corresponding EDAM formats. + EDAM formats contain the EDAM iri, label, and definition.""" + return view_edam_formats(datatypes_registry, True) + + +@router.get( + "/api/datatypes/edam_data", + summary="Returns a dictionary/map of datatypes and EDAM data", + response_description="Dictionary/map of datatypes and EDAM data", +) +async def edam_data(datatypes_registry: Registry = depends(Registry)) -> Dict[str, str]: + """Gets a map of datatypes and their corresponding EDAM data.""" + return cast(Dict[str, str], view_edam_data(datatypes_registry)) + + +@router.get( + "/api/datatypes/edam_data/detailed", + summary="Returns a dictionary of datatypes and EDAM data details", + response_description="Dictionary of EDAM data details containing the EDAM iri, label, and definition", + response_model=DatatypesEDAMDetailsDict, +) +async def edam_data_detailed(datatypes_registry: Registry = depends(Registry)): + """Gets a map of datatypes and their corresponding EDAM data. + EDAM data contains the EDAM iri, label, and definition.""" + return view_edam_data(datatypes_registry, True) diff --git a/lib/galaxy/webapps/galaxy/api/display_applications.py b/lib/galaxy/webapps/galaxy/api/display_applications.py index f626d2caee00..3ee0239a2753 100644 --- a/lib/galaxy/webapps/galaxy/api/display_applications.py +++ b/lib/galaxy/webapps/galaxy/api/display_applications.py @@ -25,34 +25,34 @@ router = Router(tags=["display_applications"]) -class FastAPIDisplay: - @router.get( - "/api/display_applications", - summary="Returns the list of display applications.", - name="display_applications_index", - ) - def index( - manager: DisplayApplicationsManager = depends(DisplayApplicationsManager), - ) -> List[DisplayApplication]: - """ - Returns the list of display applications. - """ - return manager.index() - - @router.post( - "/api/display_applications/reload", - summary="Reloads the list of display applications.", - name="display_applications_reload", - require_admin=True, - ) - def reload( - payload: Optional[Dict[str, List[str]]] = Body(default=None), - manager: DisplayApplicationsManager = depends(DisplayApplicationsManager), - ) -> ReloadFeedback: - """ - Reloads the list of display applications. - """ - payload = payload or {} - ids = payload.get("ids", []) - result = manager.reload(ids) - return result +@router.get( + "/api/display_applications", + summary="Returns the list of display applications.", + name="display_applications_index", +) +def index( + manager: DisplayApplicationsManager = depends(DisplayApplicationsManager), +) -> List[DisplayApplication]: + """ + Returns the list of display applications. + """ + return manager.index() + + +@router.post( + "/api/display_applications/reload", + summary="Reloads the list of display applications.", + name="display_applications_reload", + require_admin=True, +) +def reload( + payload: Optional[Dict[str, List[str]]] = Body(default=None), + manager: DisplayApplicationsManager = depends(DisplayApplicationsManager), +) -> ReloadFeedback: + """ + Reloads the list of display applications. + """ + payload = payload or {} + ids = payload.get("ids", []) + result = manager.reload(ids) + return result diff --git a/lib/galaxy/webapps/galaxy/api/drs.py b/lib/galaxy/webapps/galaxy/api/drs.py index b91302e30957..e4174575e470 100644 --- a/lib/galaxy/webapps/galaxy/api/drs.py +++ b/lib/galaxy/webapps/galaxy/api/drs.py @@ -38,71 +38,73 @@ DRS_SERVICE_DESCRIPTION = "Serves Galaxy datasets according to the GA4GH DRS specification" -class DrsApi: - @router.get("/ga4gh/drs/v1/service-info") - def service_info(request: Request, config: GalaxyAppConfiguration = depends(GalaxyAppConfiguration)) -> Service: - components = request.url.components - hostname = components.hostname - assert hostname - default_organization_id = ".".join(reversed(hostname.split("."))) - organization_id = config.ga4gh_service_id or default_organization_id - organization_name = config.organization_name or organization_id - organization_url = config.organization_url or f"{components.scheme}://{components.netloc}" +@router.get("/ga4gh/drs/v1/service-info") +def service_info(request: Request, config: GalaxyAppConfiguration = depends(GalaxyAppConfiguration)) -> Service: + components = request.url.components + hostname = components.hostname + assert hostname + default_organization_id = ".".join(reversed(hostname.split("."))) + organization_id = config.ga4gh_service_id or default_organization_id + organization_name = config.organization_name or organization_id + organization_url = config.organization_url or f"{components.scheme}://{components.netloc}" + + organization = ServiceOrganization( + url=organization_url, + name=organization_name, + ) + service_type = ServiceType( + group="org.ga4gh", + artifact="drs", + version="1.2.0", + ) + extra_kwds = {} + if environment := config.ga4gh_service_environment: + extra_kwds["environment"] = environment + return Service( + id=organization_id + ".drs", + name=DRS_SERVICE_NAME, + description=DRS_SERVICE_DESCRIPTION, + organization=organization, + type=service_type, + version=VERSION, + **extra_kwds, + ) - organization = ServiceOrganization( - url=organization_url, - name=organization_name, - ) - service_type = ServiceType( - group="org.ga4gh", - artifact="drs", - version="1.2.0", - ) - extra_kwds = {} - if environment := config.ga4gh_service_environment: - extra_kwds["environment"] = environment - return Service( - id=organization_id + ".drs", - name=DRS_SERVICE_NAME, - description=DRS_SERVICE_DESCRIPTION, - organization=organization, - type=service_type, - version=VERSION, - **extra_kwds, - ) - @router.get("/ga4gh/drs/v1/objects/{object_id}") - @router.post("/ga4gh/drs/v1/objects/{object_id}") # spec specifies both get and post should work. - def get_object( - request: Request, - trans: ProvidesHistoryContext = DependsOnTrans, - object_id: str = ObjectIDParam, - service: DatasetsService = depends(DatasetsService), - ) -> DrsObject: - return service.get_drs_object(trans, object_id, request_url=request.url) +@router.get("/ga4gh/drs/v1/objects/{object_id}") +@router.post("/ga4gh/drs/v1/objects/{object_id}") # spec specifies both get and post should work. +def get_object( + request: Request, + trans: ProvidesHistoryContext = DependsOnTrans, + object_id: str = ObjectIDParam, + service: DatasetsService = depends(DatasetsService), +) -> DrsObject: + return service.get_drs_object(trans, object_id, request_url=request.url) - @router.get("/ga4gh/drs/v1/objects/{object_id}/access/{access_id}") - @router.post("/ga4gh/drs/v1/objects/{object_id}/access/{access_id}") - def get_access_url( - request: Request, - trans: ProvidesHistoryContext = DependsOnTrans, - object_id: str = ObjectIDParam, - access_id: str = AccessIDParam, - ): - raise ObjectNotFound("Access IDs are not implemented for this DRS server") - @router.get( - "/api/drs_download/{object_id}", - response_class=FileResponse, +@router.get("/ga4gh/drs/v1/objects/{object_id}/access/{access_id}") +@router.post("/ga4gh/drs/v1/objects/{object_id}/access/{access_id}") +def get_access_url( + request: Request, + trans: ProvidesHistoryContext = DependsOnTrans, + object_id: str = ObjectIDParam, + access_id: str = AccessIDParam, +): + raise ObjectNotFound("Access IDs are not implemented for this DRS server") + + +@router.get( + "/api/drs_download/{object_id}", + response_class=FileResponse, +) +def download( + trans: ProvidesHistoryContext = DependsOnTrans, + object_id: str = ObjectIDParam, + service: DatasetsService = depends(DatasetsService), +): + decoded_object_id, hda_ldda = service.drs_dataset_instance(object_id) + display_data, headers = service.display( + trans, DecodedDatabaseIdField(decoded_object_id), hda_ldda=hda_ldda, filename=None, raw=True ) - def download( - trans: ProvidesHistoryContext = DependsOnTrans, - object_id: str = ObjectIDParam, - service: DatasetsService = depends(DatasetsService), - ): - decoded_object_id, hda_ldda = service.drs_dataset_instance(object_id) - display_data, headers = service.display( - trans, DecodedDatabaseIdField(decoded_object_id), hda_ldda=hda_ldda, filename=None, raw=True - ) - data_io = cast(IOBase, display_data) - return FileResponse(getattr(data_io, "name", "unnamed_file"), headers=headers) + data_io = cast(IOBase, display_data) + return FileResponse(getattr(data_io, "name", "unnamed_file"), headers=headers) diff --git a/lib/galaxy/webapps/galaxy/api/folder_contents.py b/lib/galaxy/webapps/galaxy/api/folder_contents.py index 757bbbb1b3a2..7f0419e8575f 100644 --- a/lib/galaxy/webapps/galaxy/api/folder_contents.py +++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py @@ -66,59 +66,59 @@ ) -class FastAPILibraryFoldersContents: - @router.get( - "/api/folders/{folder_id}/contents", - summary="Returns a list of a folder's contents (files and sub-folders) with additional metadata about the folder.", - responses={ - 200: { - "description": "The contents of the folder that match the query parameters.", - "model": LibraryFolderContentsIndexResult, - }, +@router.get( + "/api/folders/{folder_id}/contents", + summary="Returns a list of a folder's contents (files and sub-folders) with additional metadata about the folder.", + responses={ + 200: { + "description": "The contents of the folder that match the query parameters.", + "model": LibraryFolderContentsIndexResult, }, + }, +) +def index( + trans: ProvidesUserContext = DependsOnTrans, + folder_id: LibraryFolderDatabaseIdField = FolderIdPathParam, + limit: int = LimitQueryParam, + offset: int = OffsetQueryParam, + search_text: Optional[str] = SearchQueryParam, + include_deleted: Optional[bool] = IncludeDeletedQueryParam, + order_by: LibraryFolderContentsIndexSortByEnum = SortByQueryParam, + sort_desc: Optional[bool] = SortDescQueryParam, + service: LibraryFolderContentsService = depends(LibraryFolderContentsService), +): + """Returns a list of a folder's contents (files and sub-folders). + + Additional metadata for the folder is provided in the response as a separate object containing data + for breadcrumb path building, permissions and other folder's details. + + *Note*: When sorting, folders always have priority (they show-up before any dataset regardless of the sorting). + + **Security note**: + - Accessing a library folder or sub-folder requires only access to the parent library. + - Deleted folders can only be accessed by admins or users with `MODIFY` permission. + - Datasets may be public, private or restricted (to a group of users). Listing deleted datasets has the same requirements as folders. + """ + payload = LibraryFolderContentsIndexQueryPayload( + limit=limit, + offset=offset, + search_text=search_text, + include_deleted=include_deleted, + order_by=order_by, + sort_desc=sort_desc, ) - def index( - trans: ProvidesUserContext = DependsOnTrans, - folder_id: LibraryFolderDatabaseIdField = FolderIdPathParam, - limit: int = LimitQueryParam, - offset: int = OffsetQueryParam, - search_text: Optional[str] = SearchQueryParam, - include_deleted: Optional[bool] = IncludeDeletedQueryParam, - order_by: LibraryFolderContentsIndexSortByEnum = SortByQueryParam, - sort_desc: Optional[bool] = SortDescQueryParam, - service: LibraryFolderContentsService = depends(LibraryFolderContentsService), - ): - """Returns a list of a folder's contents (files and sub-folders). - - Additional metadata for the folder is provided in the response as a separate object containing data - for breadcrumb path building, permissions and other folder's details. - - *Note*: When sorting, folders always have priority (they show-up before any dataset regardless of the sorting). - - **Security note**: - - Accessing a library folder or sub-folder requires only access to the parent library. - - Deleted folders can only be accessed by admins or users with `MODIFY` permission. - - Datasets may be public, private or restricted (to a group of users). Listing deleted datasets has the same requirements as folders. - """ - payload = LibraryFolderContentsIndexQueryPayload( - limit=limit, - offset=offset, - search_text=search_text, - include_deleted=include_deleted, - order_by=order_by, - sort_desc=sort_desc, - ) - return service.index(trans, folder_id, payload) - - @router.post( - "/api/folders/{folder_id}/contents", - name="add_history_datasets_to_library", - summary="Creates a new library file from an existing HDA/HDCA.", - ) - def create( - trans: ProvidesUserContext = DependsOnTrans, - folder_id: LibraryFolderDatabaseIdField = FolderIdPathParam, - payload: CreateLibraryFilePayload = Body(...), - service: LibraryFolderContentsService = depends(LibraryFolderContentsService), - ): - return service.create(trans, folder_id, payload) + return service.index(trans, folder_id, payload) + + +@router.post( + "/api/folders/{folder_id}/contents", + name="add_history_datasets_to_library", + summary="Creates a new library file from an existing HDA/HDCA.", +) +def create( + trans: ProvidesUserContext = DependsOnTrans, + folder_id: LibraryFolderDatabaseIdField = FolderIdPathParam, + payload: CreateLibraryFilePayload = Body(...), + service: LibraryFolderContentsService = depends(LibraryFolderContentsService), +): + return service.create(trans, folder_id, payload) diff --git a/lib/galaxy/webapps/galaxy/api/folders.py b/lib/galaxy/webapps/galaxy/api/folders.py index ede9481e40a0..a2dc11ea3e10 100644 --- a/lib/galaxy/webapps/galaxy/api/folders.py +++ b/lib/galaxy/webapps/galaxy/api/folders.py @@ -45,113 +45,117 @@ ) -class FastAPILibraryFolders: - @router.get( - "/api/folders/{id}", - summary="Displays information about a particular library folder.", - ) - def show( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> LibraryFolderDetails: - """Returns detailed information about the library folder with the given ID.""" - return service.show(trans, id) - - @router.post( - "/api/folders/{id}", - summary="Create a new library folder underneath the one specified by the ID.", - ) - def create( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - payload: CreateLibraryFolderPayload = Body(...), - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> LibraryFolderDetails: - """Returns detailed information about the newly created library folder.""" - return service.create(trans, id, payload) - - @router.put( - "/api/folders/{id}", - summary="Updates the information of an existing library folder.", - ) - @router.patch("/api/folders/{id}") - def update( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - payload: UpdateLibraryFolderPayload = Body(...), - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> LibraryFolderDetails: - """Updates the information of an existing library folder.""" - return service.update(trans, id, payload) - - @router.delete( - "/api/folders/{id}", - summary="Marks the specified library folder as deleted (or undeleted).", - ) - def delete( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - undelete: Optional[bool] = UndeleteQueryParam, - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> LibraryFolderDetails: - """Marks the specified library folder as deleted (or undeleted).""" - return service.delete(trans, id, undelete) - - @router.get( - "/api/folders/{id}/permissions", - summary="Gets the current or available permissions of a particular library folder.", - ) - def get_permissions( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - scope: Optional[LibraryPermissionScope] = Query( - None, - title="Scope", - description="The scope of the permissions to retrieve. Either the `current` permissions or the `available`.", - ), - page: int = Query( - default=1, title="Page", description="The page number to retrieve when paginating the available roles." - ), - page_limit: int = Query( - default=10, title="Page Limit", description="The maximum number of permissions per page when paginating." - ), - q: Optional[str] = Query( - None, title="Query", description="Optional search text to retrieve only the roles matching this query." - ), - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> Union[LibraryFolderCurrentPermissions, LibraryAvailablePermissions]: - """Gets the current or available permissions of a particular library. - The results can be paginated and additionally filtered by a query.""" - return service.get_permissions( - trans, - id, - scope, - page, - page_limit, - q, - ) - - @router.post( - "/api/folders/{id}/permissions", - summary="Sets the permissions to manage a library folder.", +@router.get( + "/api/folders/{id}", + summary="Displays information about a particular library folder.", +) +def show( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> LibraryFolderDetails: + """Returns detailed information about the library folder with the given ID.""" + return service.show(trans, id) + + +@router.post( + "/api/folders/{id}", + summary="Create a new library folder underneath the one specified by the ID.", +) +def create( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + payload: CreateLibraryFolderPayload = Body(...), + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> LibraryFolderDetails: + """Returns detailed information about the newly created library folder.""" + return service.create(trans, id, payload) + + +@router.put( + "/api/folders/{id}", + summary="Updates the information of an existing library folder.", +) +@router.patch("/api/folders/{id}") +def update( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + payload: UpdateLibraryFolderPayload = Body(...), + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> LibraryFolderDetails: + """Updates the information of an existing library folder.""" + return service.update(trans, id, payload) + + +@router.delete( + "/api/folders/{id}", + summary="Marks the specified library folder as deleted (or undeleted).", +) +def delete( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + undelete: Optional[bool] = UndeleteQueryParam, + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> LibraryFolderDetails: + """Marks the specified library folder as deleted (or undeleted).""" + return service.delete(trans, id, undelete) + + +@router.get( + "/api/folders/{id}/permissions", + summary="Gets the current or available permissions of a particular library folder.", +) +def get_permissions( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + scope: Optional[LibraryPermissionScope] = Query( + None, + title="Scope", + description="The scope of the permissions to retrieve. Either the `current` permissions or the `available`.", + ), + page: int = Query( + default=1, title="Page", description="The page number to retrieve when paginating the available roles." + ), + page_limit: int = Query( + default=10, title="Page Limit", description="The maximum number of permissions per page when paginating." + ), + q: Optional[str] = Query( + None, title="Query", description="Optional search text to retrieve only the roles matching this query." + ), + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> Union[LibraryFolderCurrentPermissions, LibraryAvailablePermissions]: + """Gets the current or available permissions of a particular library. + The results can be paginated and additionally filtered by a query.""" + return service.get_permissions( + trans, + id, + scope, + page, + page_limit, + q, ) - def set_permissions( - trans: ProvidesUserContext = DependsOnTrans, - id: LibraryFolderDatabaseIdField = FolderIdPathParam, - action: Optional[LibraryFolderPermissionAction] = Query( - default=None, - title="Action", - description=( - "Indicates what action should be performed on the Library. " - f"Currently only `{LibraryFolderPermissionAction.set_permissions.value}` is supported." - ), + + +@router.post( + "/api/folders/{id}/permissions", + summary="Sets the permissions to manage a library folder.", +) +def set_permissions( + trans: ProvidesUserContext = DependsOnTrans, + id: LibraryFolderDatabaseIdField = FolderIdPathParam, + action: Optional[LibraryFolderPermissionAction] = Query( + default=None, + title="Action", + description=( + "Indicates what action should be performed on the Library. " + f"Currently only `{LibraryFolderPermissionAction.set_permissions.value}` is supported." ), - payload: LibraryFolderPermissionsPayload = Body(...), - service: LibraryFoldersService = depends(LibraryFoldersService), - ) -> LibraryFolderCurrentPermissions: - """Sets the permissions to manage a library folder.""" - payload_dict = payload.dict(by_alias=True) - if isinstance(payload, LibraryFolderPermissionsPayload) and action is not None: - payload_dict["action"] = action - return service.set_permissions(trans, id, payload_dict) + ), + payload: LibraryFolderPermissionsPayload = Body(...), + service: LibraryFoldersService = depends(LibraryFoldersService), +) -> LibraryFolderCurrentPermissions: + """Sets the permissions to manage a library folder.""" + payload_dict = payload.dict(by_alias=True) + if isinstance(payload, LibraryFolderPermissionsPayload) and action is not None: + payload_dict["action"] = action + return service.set_permissions(trans, id, payload_dict) diff --git a/lib/galaxy/webapps/galaxy/api/forms.py b/lib/galaxy/webapps/galaxy/api/forms.py index 7a6943a8264c..b2c7299458a9 100644 --- a/lib/galaxy/webapps/galaxy/api/forms.py +++ b/lib/galaxy/webapps/galaxy/api/forms.py @@ -26,24 +26,24 @@ router = Router(tags=["forms"]) -class FastAPIForms: - @router.delete("/api/forms/{id}", require_admin=True) - def delete( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - form_manager: FormManager = depends(FormManager), - ): - form = form_manager.get(trans, id) - form_manager.delete(trans, form) +@router.delete("/api/forms/{id}", require_admin=True) +def delete( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + form_manager: FormManager = depends(FormManager), +): + form = form_manager.get(trans, id) + form_manager.delete(trans, form) - @router.post("/api/forms/{id}/undelete", require_admin=True) - def undelete( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - form_manager: FormManager = depends(FormManager), - ): - form = form_manager.get(trans, id) - form_manager.undelete(trans, form) + +@router.post("/api/forms/{id}/undelete", require_admin=True) +def undelete( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + form_manager: FormManager = depends(FormManager), +): + form = form_manager.get(trans, id) + form_manager.undelete(trans, form) class FormDefinitionAPIController(BaseGalaxyAPIController): diff --git a/lib/galaxy/webapps/galaxy/api/genomes.py b/lib/galaxy/webapps/galaxy/api/genomes.py index d7e8e4822f54..6d08d5162b34 100644 --- a/lib/galaxy/webapps/galaxy/api/genomes.py +++ b/lib/galaxy/webapps/galaxy/api/genomes.py @@ -64,62 +64,62 @@ def get_id(base, format): return base -class FastAPIGenomes: - @router.get("/api/genomes", summary="Return a list of installed genomes", response_description="Installed genomes") - def index( - trans: ProvidesUserContext = DependsOnTrans, - chrom_info: bool = ChromInfoQueryParam, - manager: GenomesManager = depends(GenomesManager), - ) -> List[List[str]]: - return manager.get_dbkeys(trans.user, chrom_info) - - @router.get( - "/api/genomes/{id}", - summary="Return information about build ", - response_description="Information about genome build ", - ) - def show( - trans: ProvidesUserContext = DependsOnTrans, - id: str = IdPathParam, - reference: bool = ReferenceQueryParam, - num: int = NumQueryParam, - chrom: str = ChromQueryParam, - low: int = LowQueryParam, - high: int = HighQueryParam, - format: str = FormatQueryParam, - manager: GenomesManager = depends(GenomesManager), - ) -> Any: - id = get_id(id, format) - return manager.get_genome(trans, id, num, chrom, low, high, reference) - - @router.get( - "/api/genomes/{id}/indexes", - summary="Return all available indexes for a genome id for provided type", - response_description="Indexes for a genome id for provided type", - ) - def indexes( - id: str = IdPathParam, - type: str = IndexTypeQueryParam, - format: str = FormatQueryParam, - manager: GenomesManager = depends(GenomesManager), - ) -> Any: - id = get_id(id, format) - rval = manager.get_indexes(id, type) - return Response(rval) - - @router.get( - "/api/genomes/{id}/sequences", summary="Return raw sequence data", response_description="Raw sequence data" - ) - def sequences( - trans: ProvidesUserContext = DependsOnTrans, - id: str = IdPathParam, - reference: bool = ReferenceQueryParam, - chrom: str = ChromQueryParam, - low: int = LowQueryParam, - high: int = HighQueryParam, - format: str = FormatQueryParam, - manager: GenomesManager = depends(GenomesManager), - ) -> Any: - id = get_id(id, format) - rval = manager.get_sequence(trans, id, chrom, low, high) - return Response(rval) +@router.get("/api/genomes", summary="Return a list of installed genomes", response_description="Installed genomes") +def index( + trans: ProvidesUserContext = DependsOnTrans, + chrom_info: bool = ChromInfoQueryParam, + manager: GenomesManager = depends(GenomesManager), +) -> List[List[str]]: + return manager.get_dbkeys(trans.user, chrom_info) + + +@router.get( + "/api/genomes/{id}", + summary="Return information about build ", + response_description="Information about genome build ", +) +def show( + trans: ProvidesUserContext = DependsOnTrans, + id: str = IdPathParam, + reference: bool = ReferenceQueryParam, + num: int = NumQueryParam, + chrom: str = ChromQueryParam, + low: int = LowQueryParam, + high: int = HighQueryParam, + format: str = FormatQueryParam, + manager: GenomesManager = depends(GenomesManager), +) -> Any: + id = get_id(id, format) + return manager.get_genome(trans, id, num, chrom, low, high, reference) + + +@router.get( + "/api/genomes/{id}/indexes", + summary="Return all available indexes for a genome id for provided type", + response_description="Indexes for a genome id for provided type", +) +def indexes( + id: str = IdPathParam, + type: str = IndexTypeQueryParam, + format: str = FormatQueryParam, + manager: GenomesManager = depends(GenomesManager), +) -> Any: + id = get_id(id, format) + rval = manager.get_indexes(id, type) + return Response(rval) + + +@router.get("/api/genomes/{id}/sequences", summary="Return raw sequence data", response_description="Raw sequence data") +def sequences( + trans: ProvidesUserContext = DependsOnTrans, + id: str = IdPathParam, + reference: bool = ReferenceQueryParam, + chrom: str = ChromQueryParam, + low: int = LowQueryParam, + high: int = HighQueryParam, + format: str = FormatQueryParam, + manager: GenomesManager = depends(GenomesManager), +) -> Any: + id = get_id(id, format) + rval = manager.get_sequence(trans, id, chrom, low, high) + return Response(rval) diff --git a/lib/galaxy/webapps/galaxy/api/group_roles.py b/lib/galaxy/webapps/galaxy/api/group_roles.py index f0177d4900ed..9b258e54727a 100644 --- a/lib/galaxy/webapps/galaxy/api/group_roles.py +++ b/lib/galaxy/webapps/galaxy/api/group_roles.py @@ -35,52 +35,54 @@ def group_role_to_model(trans, group_id: int, role) -> GroupRoleResponse: return GroupRoleResponse(id=role.id, name=role.name, url=url) -class FastAPIGroupRoles: - @router.get( - "/api/groups/{group_id}/roles", - require_admin=True, - summary="Displays a collection (list) of groups.", - name="group_roles", - ) - def index( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - manager: GroupRolesManager = depends(GroupRolesManager), - ) -> GroupRoleListResponse: - group_roles = manager.index(trans, group_id) - return GroupRoleListResponse(__root__=[group_role_to_model(trans, group_id, gr.role) for gr in group_roles]) - - @router.get( - "/api/groups/{group_id}/roles/{role_id}", - name="group_role", - require_admin=True, - summary="Displays information about a group role.", - ) - def show( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - role_id: DecodedDatabaseIdField = RoleIDParam, - manager: GroupRolesManager = depends(GroupRolesManager), - ) -> GroupRoleResponse: - role = manager.show(trans, role_id, group_id) - return group_role_to_model(trans, group_id, role) - - @router.put("/api/groups/{group_id}/roles/{role_id}", require_admin=True, summary="Adds a role to a group") - def update( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - role_id: DecodedDatabaseIdField = RoleIDParam, - manager: GroupRolesManager = depends(GroupRolesManager), - ) -> GroupRoleResponse: - role = manager.update(trans, role_id, group_id) - return group_role_to_model(trans, group_id, role) - - @router.delete("/api/groups/{group_id}/roles/{role_id}", require_admin=True, summary="Removes a role from a group") - def delete( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - role_id: DecodedDatabaseIdField = RoleIDParam, - manager: GroupRolesManager = depends(GroupRolesManager), - ) -> GroupRoleResponse: - role = manager.delete(trans, role_id, group_id) - return group_role_to_model(trans, group_id, role) +@router.get( + "/api/groups/{group_id}/roles", + require_admin=True, + summary="Displays a collection (list) of groups.", + name="group_roles", +) +def index( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + manager: GroupRolesManager = depends(GroupRolesManager), +) -> GroupRoleListResponse: + group_roles = manager.index(trans, group_id) + return GroupRoleListResponse(__root__=[group_role_to_model(trans, group_id, gr.role) for gr in group_roles]) + + +@router.get( + "/api/groups/{group_id}/roles/{role_id}", + name="group_role", + require_admin=True, + summary="Displays information about a group role.", +) +def show( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + role_id: DecodedDatabaseIdField = RoleIDParam, + manager: GroupRolesManager = depends(GroupRolesManager), +) -> GroupRoleResponse: + role = manager.show(trans, role_id, group_id) + return group_role_to_model(trans, group_id, role) + + +@router.put("/api/groups/{group_id}/roles/{role_id}", require_admin=True, summary="Adds a role to a group") +def update( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + role_id: DecodedDatabaseIdField = RoleIDParam, + manager: GroupRolesManager = depends(GroupRolesManager), +) -> GroupRoleResponse: + role = manager.update(trans, role_id, group_id) + return group_role_to_model(trans, group_id, role) + + +@router.delete("/api/groups/{group_id}/roles/{role_id}", require_admin=True, summary="Removes a role from a group") +def delete( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + role_id: DecodedDatabaseIdField = RoleIDParam, + manager: GroupRolesManager = depends(GroupRolesManager), +) -> GroupRoleResponse: + role = manager.delete(trans, role_id, group_id) + return group_role_to_model(trans, group_id, role) diff --git a/lib/galaxy/webapps/galaxy/api/group_users.py b/lib/galaxy/webapps/galaxy/api/group_users.py index 435f1183f15a..05756b45b231 100644 --- a/lib/galaxy/webapps/galaxy/api/group_users.py +++ b/lib/galaxy/webapps/galaxy/api/group_users.py @@ -34,78 +34,80 @@ def group_user_to_model(trans, group_id, user) -> GroupUserResponse: return GroupUserResponse(id=user.id, email=user.email, url=url) -class FastAPIGroupUsers: - @router.get( - "/api/groups/{group_id}/users", - require_admin=True, - summary="Displays a collection (list) of groups.", - name="group_users", - ) - def index( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - manager: GroupUsersManager = depends(GroupUsersManager), - ) -> GroupUserListResponse: - """ - GET /api/groups/{encoded_group_id}/users - Displays a collection (list) of groups. - """ - group_users = manager.index(trans, group_id) - return GroupUserListResponse(__root__=[group_user_to_model(trans, group_id, gr) for gr in group_users]) - - @router.get( - "/api/groups/{group_id}/user/{user_id}", - alias="/api/groups/{group_id}/users/{user_id}", - name="group_user", - require_admin=True, - summary="Displays information about a group user.", - ) - def show( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - user_id: DecodedDatabaseIdField = UserIDParam, - manager: GroupUsersManager = depends(GroupUsersManager), - ) -> GroupUserResponse: - """ - Displays information about a group user. - """ - user = manager.show(trans, user_id, group_id) - return group_user_to_model(trans, group_id, user) - - @router.put( - "/api/groups/{group_id}/users/{user_id}", - alias="/api/groups/{group_id}/user/{user_id}", - require_admin=True, - summary="Adds a user to a group", - ) - def update( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - user_id: DecodedDatabaseIdField = UserIDParam, - manager: GroupUsersManager = depends(GroupUsersManager), - ) -> GroupUserResponse: - """ - PUT /api/groups/{encoded_group_id}/users/{encoded_user_id} - Adds a user to a group - """ - user = manager.update(trans, user_id, group_id) - return group_user_to_model(trans, group_id, user) - - @router.delete( - "/api/groups/{group_id}/user/{user_id}", - alias="/api/groups/{group_id}/users/{user_id}", - require_admin=True, - summary="Removes a user from a group", - ) - def delete( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = GroupIDParam, - user_id: DecodedDatabaseIdField = UserIDParam, - manager: GroupUsersManager = depends(GroupUsersManager), - ) -> GroupUserResponse: - """ - DELETE /api/groups/{encoded_group_id}/users/{encoded_user_id} - Removes a user from a group - """ - user = manager.delete(trans, user_id, group_id) - return group_user_to_model(trans, group_id, user) +@router.get( + "/api/groups/{group_id}/users", + require_admin=True, + summary="Displays a collection (list) of groups.", + name="group_users", +) +def index( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + manager: GroupUsersManager = depends(GroupUsersManager), +) -> GroupUserListResponse: + """ + GET /api/groups/{encoded_group_id}/users + Displays a collection (list) of groups. + """ + group_users = manager.index(trans, group_id) + return GroupUserListResponse(__root__=[group_user_to_model(trans, group_id, gr) for gr in group_users]) + + +@router.get( + "/api/groups/{group_id}/user/{user_id}", + alias="/api/groups/{group_id}/users/{user_id}", + name="group_user", + require_admin=True, + summary="Displays information about a group user.", +) +def show( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + user_id: DecodedDatabaseIdField = UserIDParam, + manager: GroupUsersManager = depends(GroupUsersManager), +) -> GroupUserResponse: + """ + Displays information about a group user. + """ + user = manager.show(trans, user_id, group_id) + return group_user_to_model(trans, group_id, user) + + +@router.put( + "/api/groups/{group_id}/users/{user_id}", + alias="/api/groups/{group_id}/user/{user_id}", + require_admin=True, + summary="Adds a user to a group", +) +def update( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + user_id: DecodedDatabaseIdField = UserIDParam, + manager: GroupUsersManager = depends(GroupUsersManager), +) -> GroupUserResponse: + """ + PUT /api/groups/{encoded_group_id}/users/{encoded_user_id} + Adds a user to a group + """ + user = manager.update(trans, user_id, group_id) + return group_user_to_model(trans, group_id, user) + + +@router.delete( + "/api/groups/{group_id}/user/{user_id}", + alias="/api/groups/{group_id}/users/{user_id}", + require_admin=True, + summary="Removes a user from a group", +) +def delete( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = GroupIDParam, + user_id: DecodedDatabaseIdField = UserIDParam, + manager: GroupUsersManager = depends(GroupUsersManager), +) -> GroupUserResponse: + """ + DELETE /api/groups/{encoded_group_id}/users/{encoded_user_id} + Removes a user from a group + """ + user = manager.delete(trans, user_id, group_id) + return group_user_to_model(trans, group_id, user) diff --git a/lib/galaxy/webapps/galaxy/api/groups.py b/lib/galaxy/webapps/galaxy/api/groups.py index f4f1aa42c4ba..6a7c2f4e1133 100644 --- a/lib/galaxy/webapps/galaxy/api/groups.py +++ b/lib/galaxy/webapps/galaxy/api/groups.py @@ -27,79 +27,84 @@ router = Router(tags=["groups"]) -class FastAPIGroups: - @router.get( - "/api/groups", - summary="Displays a collection (list) of groups.", - require_admin=True, - response_model_exclude_unset=True, - ) - def index( - trans: ProvidesAppContext = DependsOnTrans, - manager: GroupsManager = depends(GroupsManager), - ) -> GroupListResponse: - return manager.index(trans) - - @router.post( - "/api/groups", - summary="Creates a new group.", - require_admin=True, - response_model_exclude_unset=True, - ) - def create( - trans: ProvidesAppContext = DependsOnTrans, - payload: GroupCreatePayload = Body(...), - manager: GroupsManager = depends(GroupsManager), - ) -> GroupListResponse: - return manager.create(trans, payload) - - @router.get( - "/api/groups/{group_id}", - summary="Displays information about a group.", - require_admin=True, - name="show_group", - ) - def show( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = Path(...), - manager: GroupsManager = depends(GroupsManager), - ) -> GroupResponse: - return manager.show(trans, group_id) - - @router.put( - "/api/groups/{group_id}", - summary="Modifies a group.", - require_admin=True, - response_model_exclude_unset=True, - ) - def update( - trans: ProvidesAppContext = DependsOnTrans, - group_id: DecodedDatabaseIdField = Path(...), - payload: GroupCreatePayload = Body(...), - manager: GroupsManager = depends(GroupsManager), - ) -> GroupResponse: - return manager.update(trans, group_id, payload) - - @router.delete("/api/groups/{group_id}", require_admin=True) - def delete( - group_id: DecodedDatabaseIdField, - trans: ProvidesAppContext = DependsOnTrans, - manager: GroupsManager = depends(GroupsManager), - ): - manager.delete(trans, group_id) - - @router.post("/api/groups/{group_id}/purge", require_admin=True) - def purge( - group_id: DecodedDatabaseIdField, - trans: ProvidesAppContext = DependsOnTrans, - manager: GroupsManager = depends(GroupsManager), - ): - manager.purge(trans, group_id) - - @router.post("/api/groups/{group_id}/undelete", require_admin=True) - def undelete( - group_id: DecodedDatabaseIdField, - trans: ProvidesAppContext = DependsOnTrans, - manager: GroupsManager = depends(GroupsManager), - ): - manager.undelete(trans, group_id) +@router.get( + "/api/groups", + summary="Displays a collection (list) of groups.", + require_admin=True, + response_model_exclude_unset=True, +) +def index( + trans: ProvidesAppContext = DependsOnTrans, + manager: GroupsManager = depends(GroupsManager), +) -> GroupListResponse: + return manager.index(trans) + + +@router.post( + "/api/groups", + summary="Creates a new group.", + require_admin=True, + response_model_exclude_unset=True, +) +def create( + trans: ProvidesAppContext = DependsOnTrans, + payload: GroupCreatePayload = Body(...), + manager: GroupsManager = depends(GroupsManager), +) -> GroupListResponse: + return manager.create(trans, payload) + + +@router.get( + "/api/groups/{group_id}", + summary="Displays information about a group.", + require_admin=True, + name="show_group", +) +def show( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = Path(...), + manager: GroupsManager = depends(GroupsManager), +) -> GroupResponse: + return manager.show(trans, group_id) + + +@router.put( + "/api/groups/{group_id}", + summary="Modifies a group.", + require_admin=True, + response_model_exclude_unset=True, +) +def update( + trans: ProvidesAppContext = DependsOnTrans, + group_id: DecodedDatabaseIdField = Path(...), + payload: GroupCreatePayload = Body(...), + manager: GroupsManager = depends(GroupsManager), +) -> GroupResponse: + return manager.update(trans, group_id, payload) + + +@router.delete("/api/groups/{group_id}", require_admin=True) +def delete( + group_id: DecodedDatabaseIdField, + trans: ProvidesAppContext = DependsOnTrans, + manager: GroupsManager = depends(GroupsManager), +): + manager.delete(trans, group_id) + + +@router.post("/api/groups/{group_id}/purge", require_admin=True) +def purge( + group_id: DecodedDatabaseIdField, + trans: ProvidesAppContext = DependsOnTrans, + manager: GroupsManager = depends(GroupsManager), +): + manager.purge(trans, group_id) + + +@router.post("/api/groups/{group_id}/undelete", require_admin=True) +def undelete( + group_id: DecodedDatabaseIdField, + trans: ProvidesAppContext = DependsOnTrans, + manager: GroupsManager = depends(GroupsManager), +): + manager.undelete(trans, group_id) diff --git a/lib/galaxy/webapps/galaxy/api/help.py b/lib/galaxy/webapps/galaxy/api/help.py index 0a815c8faacf..3f76e362011b 100644 --- a/lib/galaxy/webapps/galaxy/api/help.py +++ b/lib/galaxy/webapps/galaxy/api/help.py @@ -16,17 +16,16 @@ router = Router(tags=["help"]) -class HelpAPI: - @router.get( - "/api/help/forum/search", - summary="Search the Galaxy Help forum.", - ) - def search_forum( - query: Annotated[str, Query(description="Search query to use for searching the Galaxy Help forum.")], - service: HelpService = depends(HelpService), - ) -> HelpForumSearchResponse: - """Search the Galaxy Help forum using the Discourse API. - - **Note**: This endpoint is for **INTERNAL USE ONLY** and is not part of the public Galaxy API. - """ - return service.search_forum(query) +@router.get( + "/api/help/forum/search", + summary="Search the Galaxy Help forum.", +) +def search_forum( + query: Annotated[str, Query(description="Search query to use for searching the Galaxy Help forum.")], + service: HelpService = depends(HelpService), +) -> HelpForumSearchResponse: + """Search the Galaxy Help forum using the Discourse API. + + **Note**: This endpoint is for **INTERNAL USE ONLY** and is not part of the public Galaxy API. + """ + return service.search_forum(query) diff --git a/lib/galaxy/webapps/galaxy/api/job_tokens.py b/lib/galaxy/webapps/galaxy/api/job_tokens.py index 7708063fb2c3..761fdd47c8af 100644 --- a/lib/galaxy/webapps/galaxy/api/job_tokens.py +++ b/lib/galaxy/webapps/galaxy/api/job_tokens.py @@ -22,32 +22,31 @@ router = Router(tags=["remote files"]) -class FastAPIJobTokens: - @router.get( - "/api/jobs/{job_id}/oidc-tokens", - summary="Get a fresh OIDC token", - description="Allows remote job running mechanisms to get a fresh OIDC token that " - "can be used on remote side to authorize user. " - "It is not meant to represent part of Galaxy's stable, user facing API", - tags=["oidc_tokens"], - response_class=PlainTextResponse, - ) - def get_token( - job_id: EncodedDatabaseIdField, - job_key: str = Query( - description=( - "A key used to authenticate this request as acting on" "behalf or a job runner for the specified job" - ), - ), - provider: str = Query( - description=("OIDC provider name"), +@router.get( + "/api/jobs/{job_id}/oidc-tokens", + summary="Get a fresh OIDC token", + description="Allows remote job running mechanisms to get a fresh OIDC token that " + "can be used on remote side to authorize user. " + "It is not meant to represent part of Galaxy's stable, user facing API", + tags=["oidc_tokens"], + response_class=PlainTextResponse, +) +def get_token( + job_id: EncodedDatabaseIdField, + job_key: str = Query( + description=( + "A key used to authenticate this request as acting on" "behalf or a job runner for the specified job" ), - trans: ProvidesAppContext = DependsOnTrans, - ) -> str: - job = _authorize_job_access(trans, job_id, job_key) - trans.app.authnz_manager.refresh_expiring_oidc_tokens(trans, job.user) # type: ignore[attr-defined] - tokens = job.user.get_oidc_tokens(provider_name_to_backend(provider)) - return tokens["id"] + ), + provider: str = Query( + description=("OIDC provider name"), + ), + trans: ProvidesAppContext = DependsOnTrans, +) -> str: + job = _authorize_job_access(trans, job_id, job_key) + trans.app.authnz_manager.refresh_expiring_oidc_tokens(trans, job.user) # type: ignore[attr-defined] + tokens = job.user.get_oidc_tokens(provider_name_to_backend(provider)) + return tokens["id"] def _authorize_job_access(trans, encoded_job_id, job_key): diff --git a/lib/galaxy/webapps/galaxy/api/libraries.py b/lib/galaxy/webapps/galaxy/api/libraries.py index 2cb0c63c9975..cb373a778778 100644 --- a/lib/galaxy/webapps/galaxy/api/libraries.py +++ b/lib/galaxy/webapps/galaxy/api/libraries.py @@ -55,160 +55,167 @@ ) -class FastAPILibraries: - @router.get( - "/api/libraries", - summary="Returns a list of summary data for all libraries.", - ) - def index( - trans: ProvidesUserContext = DependsOnTrans, - deleted: Optional[bool] = DeletedQueryParam, - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummaryList: - """Returns a list of summary data for all libraries.""" - return service.index(trans, deleted) - - @router.get( - "/api/libraries/deleted", - summary="Returns a list of summary data for all libraries marked as deleted.", - ) - def index_deleted( - trans: ProvidesUserContext = DependsOnTrans, - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummaryList: - """Returns a list of summary data for all libraries marked as deleted.""" - return service.index(trans, True) - - @router.get( - "/api/libraries/{id}", - summary="Returns summary information about a particular library.", - ) - def show( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = LibraryIdPathParam, - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummary: - """Returns summary information about a particular library.""" - return service.show(trans, id) - - @router.post( - "/api/libraries", - summary="Creates a new library and returns its summary information.", - require_admin=True, - ) - def create( - trans: ProvidesUserContext = DependsOnTrans, - payload: CreateLibraryPayload = Body(...), - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummary: - """Creates a new library and returns its summary information. Currently, only admin users can create libraries.""" - return service.create(trans, payload) - - @router.post( - "/api/libraries/from_store", - summary="Create libraries from a model store.", - require_admin=True, - ) - def create_from_store( - trans: ProvidesUserContext = DependsOnTrans, - payload: CreateLibrariesFromStore = Body(...), - service: LibrariesService = depends(LibrariesService), - ) -> List[LibrarySummary]: - return service.create_from_store(trans, payload) - - @router.patch( - "/api/libraries/{id}", - summary="Updates the information of an existing library.", - ) - def update( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = LibraryIdPathParam, - payload: UpdateLibraryPayload = Body(...), - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummary: - """ - Updates the information of an existing library. - """ - return service.update(trans, id, payload) - - @router.delete( - "/api/libraries/{id}", - summary="Marks the specified library as deleted (or undeleted).", - require_admin=True, - ) - def delete( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = LibraryIdPathParam, - undelete: Optional[bool] = UndeleteQueryParam, - payload: Optional[DeleteLibraryPayload] = Body(default=None), - service: LibrariesService = depends(LibrariesService), - ) -> LibrarySummary: - """Marks the specified library as deleted (or undeleted). - Currently, only admin users can delete or restore libraries.""" - if payload: - undelete = payload.undelete - return service.delete(trans, id, undelete) - - @router.get( - "/api/libraries/{id}/permissions", - summary="Gets the current or available permissions of a particular library.", - ) - def get_permissions( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = LibraryIdPathParam, - scope: Optional[LibraryPermissionScope] = Query( - None, - title="Scope", - description="The scope of the permissions to retrieve. Either the `current` permissions or the `available`.", - ), - is_library_access: Optional[bool] = Query( - None, - title="Is Library Access", - description="Indicates whether the roles available for the library access are requested.", - ), - page: int = Query( - default=1, title="Page", description="The page number to retrieve when paginating the available roles." - ), - page_limit: int = Query( - default=10, title="Page Limit", description="The maximum number of permissions per page when paginating." - ), - q: Optional[str] = Query( - None, title="Query", description="Optional search text to retrieve only the roles matching this query." - ), - service: LibrariesService = depends(LibrariesService), - ) -> Union[LibraryCurrentPermissions, LibraryAvailablePermissions]: - """Gets the current or available permissions of a particular library. - The results can be paginated and additionally filtered by a query.""" - return service.get_permissions( - trans, - id, - scope, - is_library_access, - page, - page_limit, - q, - ) - - @router.post( - "/api/libraries/{id}/permissions", - summary="Sets the permissions to access and manipulate a library.", +@router.get( + "/api/libraries", + summary="Returns a list of summary data for all libraries.", +) +def index( + trans: ProvidesUserContext = DependsOnTrans, + deleted: Optional[bool] = DeletedQueryParam, + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummaryList: + """Returns a list of summary data for all libraries.""" + return service.index(trans, deleted) + + +@router.get( + "/api/libraries/deleted", + summary="Returns a list of summary data for all libraries marked as deleted.", +) +def index_deleted( + trans: ProvidesUserContext = DependsOnTrans, + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummaryList: + """Returns a list of summary data for all libraries marked as deleted.""" + return service.index(trans, True) + + +@router.get( + "/api/libraries/{id}", + summary="Returns summary information about a particular library.", +) +def show( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = LibraryIdPathParam, + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummary: + """Returns summary information about a particular library.""" + return service.show(trans, id) + + +@router.post( + "/api/libraries", + summary="Creates a new library and returns its summary information.", + require_admin=True, +) +def create( + trans: ProvidesUserContext = DependsOnTrans, + payload: CreateLibraryPayload = Body(...), + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummary: + """Creates a new library and returns its summary information. Currently, only admin users can create libraries.""" + return service.create(trans, payload) + + +@router.post( + "/api/libraries/from_store", + summary="Create libraries from a model store.", + require_admin=True, +) +def create_from_store( + trans: ProvidesUserContext = DependsOnTrans, + payload: CreateLibrariesFromStore = Body(...), + service: LibrariesService = depends(LibrariesService), +) -> List[LibrarySummary]: + return service.create_from_store(trans, payload) + + +@router.patch( + "/api/libraries/{id}", + summary="Updates the information of an existing library.", +) +def update( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = LibraryIdPathParam, + payload: UpdateLibraryPayload = Body(...), + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummary: + """ + Updates the information of an existing library. + """ + return service.update(trans, id, payload) + + +@router.delete( + "/api/libraries/{id}", + summary="Marks the specified library as deleted (or undeleted).", + require_admin=True, +) +def delete( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = LibraryIdPathParam, + undelete: Optional[bool] = UndeleteQueryParam, + payload: Optional[DeleteLibraryPayload] = Body(default=None), + service: LibrariesService = depends(LibrariesService), +) -> LibrarySummary: + """Marks the specified library as deleted (or undeleted). + Currently, only admin users can delete or restore libraries.""" + if payload: + undelete = payload.undelete + return service.delete(trans, id, undelete) + + +@router.get( + "/api/libraries/{id}/permissions", + summary="Gets the current or available permissions of a particular library.", +) +def get_permissions( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = LibraryIdPathParam, + scope: Optional[LibraryPermissionScope] = Query( + None, + title="Scope", + description="The scope of the permissions to retrieve. Either the `current` permissions or the `available`.", + ), + is_library_access: Optional[bool] = Query( + None, + title="Is Library Access", + description="Indicates whether the roles available for the library access are requested.", + ), + page: int = Query( + default=1, title="Page", description="The page number to retrieve when paginating the available roles." + ), + page_limit: int = Query( + default=10, title="Page Limit", description="The maximum number of permissions per page when paginating." + ), + q: Optional[str] = Query( + None, title="Query", description="Optional search text to retrieve only the roles matching this query." + ), + service: LibrariesService = depends(LibrariesService), +) -> Union[LibraryCurrentPermissions, LibraryAvailablePermissions]: + """Gets the current or available permissions of a particular library. + The results can be paginated and additionally filtered by a query.""" + return service.get_permissions( + trans, + id, + scope, + is_library_access, + page, + page_limit, + q, ) - def set_permissions( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = LibraryIdPathParam, - action: Optional[LibraryPermissionAction] = Query( - default=None, - title="Action", - description="Indicates what action should be performed on the Library.", - ), - payload: Union[ - LibraryPermissionsPayload, - LegacyLibraryPermissionsPayload, - ] = Body(...), - service: LibrariesService = depends(LibrariesService), - ) -> Union[LibraryLegacySummary, LibraryCurrentPermissions]: # Old legacy response - """Sets the permissions to access and manipulate a library.""" - payload_dict = payload.dict(by_alias=True) - if isinstance(payload, LibraryPermissionsPayload) and action is not None: - payload_dict["action"] = action - return service.set_permissions(trans, id, payload_dict) + + +@router.post( + "/api/libraries/{id}/permissions", + summary="Sets the permissions to access and manipulate a library.", +) +def set_permissions( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = LibraryIdPathParam, + action: Optional[LibraryPermissionAction] = Query( + default=None, + title="Action", + description="Indicates what action should be performed on the Library.", + ), + payload: Union[ + LibraryPermissionsPayload, + LegacyLibraryPermissionsPayload, + ] = Body(...), + service: LibrariesService = depends(LibrariesService), +) -> Union[LibraryLegacySummary, LibraryCurrentPermissions]: # Old legacy response + """Sets the permissions to access and manipulate a library.""" + payload_dict = payload.dict(by_alias=True) + if isinstance(payload, LibraryPermissionsPayload) and action is not None: + payload_dict["action"] = action + return service.set_permissions(trans, id, payload_dict) diff --git a/lib/galaxy/webapps/galaxy/api/licenses.py b/lib/galaxy/webapps/galaxy/api/licenses.py index 3fc1a2b54d2a..339b609c0f62 100644 --- a/lib/galaxy/webapps/galaxy/api/licenses.py +++ b/lib/galaxy/webapps/galaxy/api/licenses.py @@ -21,22 +21,18 @@ ) -class FastAPILicenses: - @router.get( - "/api/licenses", summary="Lists all available SPDX licenses", response_description="List of SPDX licenses" - ) - async def index(licenses_manager: LicensesManager = depends(LicensesManager)) -> List[LicenseMetadataModel]: - """Returns an index with all the available [SPDX licenses](https://spdx.org/licenses/).""" - return licenses_manager.get_licenses() - - @router.get( - "/api/licenses/{id}", - summary="Gets the SPDX license metadata associated with the short identifier", - response_description="SPDX license metadata", - ) - async def get( - id=LicenseIdPath, licenses_manager: LicensesManager = depends(LicensesManager) - ) -> LicenseMetadataModel: - """Returns the license metadata associated with the given - [SPDX license short ID](https://spdx.github.io/spdx-spec/appendix-I-SPDX-license-list/).""" - return licenses_manager.get_license_by_id(id) +@router.get("/api/licenses", summary="Lists all available SPDX licenses", response_description="List of SPDX licenses") +async def index(licenses_manager: LicensesManager = depends(LicensesManager)) -> List[LicenseMetadataModel]: + """Returns an index with all the available [SPDX licenses](https://spdx.org/licenses/).""" + return licenses_manager.get_licenses() + + +@router.get( + "/api/licenses/{id}", + summary="Gets the SPDX license metadata associated with the short identifier", + response_description="SPDX license metadata", +) +async def get(id=LicenseIdPath, licenses_manager: LicensesManager = depends(LicensesManager)) -> LicenseMetadataModel: + """Returns the license metadata associated with the given + [SPDX license short ID](https://spdx.github.io/spdx-spec/appendix-I-SPDX-license-list/).""" + return licenses_manager.get_license_by_id(id) diff --git a/lib/galaxy/webapps/galaxy/api/metrics.py b/lib/galaxy/webapps/galaxy/api/metrics.py index 4c4938da00d5..34f92ce07c8f 100644 --- a/lib/galaxy/webapps/galaxy/api/metrics.py +++ b/lib/galaxy/webapps/galaxy/api/metrics.py @@ -26,15 +26,14 @@ router = Router(tags=["metrics"]) -class FastAPIMetrics: - @router.post( - "/api/metrics", - summary="Records a collection of metrics.", - ) - def create( - trans: ProvidesUserContext = DependsOnTrans, - payload: CreateMetricsPayload = Body(...), - manager: MetricsManager = depends(MetricsManager), - ) -> Any: - """Record any metrics sent and return some status object.""" - return manager.create(trans, payload) +@router.post( + "/api/metrics", + summary="Records a collection of metrics.", +) +def create( + trans: ProvidesUserContext = DependsOnTrans, + payload: CreateMetricsPayload = Body(...), + manager: MetricsManager = depends(MetricsManager), +) -> Any: + """Record any metrics sent and return some status object.""" + return manager.create(trans, payload) diff --git a/lib/galaxy/webapps/galaxy/api/notifications.py b/lib/galaxy/webapps/galaxy/api/notifications.py index 38d5941a3dad..a6e4034e10bc 100644 --- a/lib/galaxy/webapps/galaxy/api/notifications.py +++ b/lib/galaxy/webapps/galaxy/api/notifications.py @@ -45,204 +45,216 @@ router = Router(tags=["notifications"]) -class FastAPINotifications: - @router.get( - "/api/notifications/status", - summary="Returns the current status summary of the user's notifications since a particular date.", - ) - def get_notifications_status( - trans: ProvidesUserContext = DependsOnTrans, - since: OffsetNaiveDatetime = Query(), - service: NotificationService = depends(NotificationService), - ) -> NotificationStatusSummary: - """Anonymous users cannot receive personal notifications, only broadcasted notifications.""" - return service.get_notifications_status(trans, since) - - @router.get( - "/api/notifications/preferences", - summary="Returns the current user's preferences for notifications.", - ) - def get_notification_preferences( - trans: ProvidesUserContext = DependsOnTrans, - service: NotificationService = depends(NotificationService), - ) -> UserNotificationPreferences: - """Anonymous users cannot have notification preferences. They will receive only broadcasted notifications.""" - return service.get_user_notification_preferences(trans) - - @router.put( - "/api/notifications/preferences", - summary="Updates the user's preferences for notifications.", - ) - def update_notification_preferences( - trans: ProvidesUserContext = DependsOnTrans, - payload: UpdateUserNotificationPreferencesRequest = Body(), - service: NotificationService = depends(NotificationService), - ) -> UserNotificationPreferences: - """Anonymous users cannot have notification preferences. They will receive only broadcasted notifications. - - - Can be used to completely enable/disable notifications for a particular type (category) - or to enable/disable a particular channel on each category. - """ - return service.update_user_notification_preferences(trans, payload) - - @router.get( - "/api/notifications", - summary="Returns the list of notifications associated with the user.", - ) - def get_user_notifications( - trans: ProvidesUserContext = DependsOnTrans, - limit: Optional[int] = 20, - offset: Optional[int] = None, - service: NotificationService = depends(NotificationService), - ) -> UserNotificationListResponse: - """Anonymous users cannot receive personal notifications, only broadcasted notifications. - - You can use the `limit` and `offset` parameters to paginate through the notifications. - """ - return service.get_user_notifications(trans, limit=limit, offset=offset) - - @router.get( - "/api/notifications/broadcast/{notification_id}", - summary="Returns the information of a specific broadcasted notification.", - ) - def get_broadcasted( - trans: ProvidesUserContext = DependsOnTrans, - notification_id: DecodedDatabaseIdField = Path(), - service: NotificationService = depends(NotificationService), - ) -> BroadcastNotificationResponse: - """Only Admin users can access inactive notifications (scheduled or recently expired).""" - return service.get_broadcasted_notification(trans, notification_id) - - @router.get( - "/api/notifications/broadcast", - summary="Returns all currently active broadcasted notifications.", - ) - def get_all_broadcasted( - trans: ProvidesUserContext = DependsOnTrans, - service: NotificationService = depends(NotificationService), - ) -> BroadcastNotificationListResponse: - """Only Admin users can access inactive notifications (scheduled or recently expired).""" - return service.get_all_broadcasted_notifications(trans) - - @router.get( - "/api/notifications/{notification_id}", - summary="Displays information about a notification received by the user.", - ) - def show_notification( - trans: ProvidesUserContext = DependsOnTrans, - notification_id: DecodedDatabaseIdField = Path(), - service: NotificationService = depends(NotificationService), - ) -> UserNotificationResponse: - user = service.get_authenticated_user(trans) - return service.get_user_notification(user, notification_id) - - @router.put( - "/api/notifications/broadcast/{notification_id}", - summary="Updates the state of a broadcasted notification.", - require_admin=True, - status_code=status.HTTP_204_NO_CONTENT, - ) - def update_broadcasted_notification( - trans: ProvidesUserContext = DependsOnTrans, - notification_id: DecodedDatabaseIdField = Path(), - payload: NotificationBroadcastUpdateRequest = Body(), - service: NotificationService = depends(NotificationService), - ): - """Only Admins can update broadcasted notifications. This is useful to reschedule, edit or expire broadcasted notifications.""" - service.update_broadcasted_notification(trans, notification_id, payload) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.put( - "/api/notifications/{notification_id}", - summary="Updates the state of a notification received by the user.", - status_code=status.HTTP_204_NO_CONTENT, - ) - def update_user_notification( - trans: ProvidesUserContext = DependsOnTrans, - notification_id: DecodedDatabaseIdField = Path(), - payload: UserNotificationUpdateRequest = Body(), - service: NotificationService = depends(NotificationService), - ): - service.update_user_notification(trans, notification_id, payload) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.put( - "/api/notifications", - summary="Updates a list of notifications with the requested values in a single request.", - ) - def update_user_notifications( - trans: ProvidesUserContext = DependsOnTrans, - payload: UserNotificationsBatchUpdateRequest = Body(), - service: NotificationService = depends(NotificationService), - ) -> NotificationsBatchUpdateResponse: - return service.update_user_notifications(trans, set(payload.notification_ids), payload.changes) - - @router.delete( - "/api/notifications/{notification_id}", - summary="Deletes a notification received by the user.", - status_code=status.HTTP_204_NO_CONTENT, - ) - def delete_user_notification( - trans: ProvidesUserContext = DependsOnTrans, - notification_id: DecodedDatabaseIdField = Path(), - service: NotificationService = depends(NotificationService), - ): - """When a notification is deleted, it is not immediately removed from the database, but marked as deleted. - - - It will not be returned in the list of notifications, but admins can still access it as long as it is not expired. - - It will be eventually removed from the database by a background task after the expiration time. - - Deleted notifications will be permanently deleted when the expiration time is reached. - """ - delete_request = UserNotificationUpdateRequest(deleted=True) - service.update_user_notification(trans, notification_id, delete_request) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.delete( - "/api/notifications", - summary="Deletes a list of notifications received by the user in a single request.", - ) - def delete_user_notifications( - trans: ProvidesUserContext = DependsOnTrans, - payload: NotificationsBatchRequest = Body(), - service: NotificationService = depends(NotificationService), - ) -> NotificationsBatchUpdateResponse: - delete_request = UserNotificationUpdateRequest(deleted=True) - return service.update_user_notifications(trans, set(payload.notification_ids), delete_request) - - @router.post( - "/api/notifications", - summary="Sends a notification to a list of recipients (users, groups or roles).", - require_admin=True, - ) - def send_notification( - trans: ProvidesUserContext = DependsOnTrans, - payload: NotificationCreateRequest = Body(), - service: NotificationService = depends(NotificationService), - ) -> NotificationCreatedResponse: - """Sends a notification to a list of recipients (users, groups or roles).""" - return service.send_notification(sender_context=trans, payload=payload) - - @router.post( - "/api/notifications/broadcast", - summary="Broadcasts a notification to every user in the system.", - require_admin=True, - ) - def broadcast_notification( - trans: ProvidesUserContext = DependsOnTrans, - payload: BroadcastNotificationCreateRequest = Body(), - service: NotificationService = depends(NotificationService), - ) -> NotificationCreatedResponse: - """Broadcasted notifications are a special kind of notification that are always accessible to all users, including anonymous users. - They are typically used to display important information such as maintenance windows or new features. - These notifications are displayed differently from regular notifications, usually in a banner at the top or bottom of the page. - - Broadcasted notifications can include action links that are displayed as buttons. - This allows users to easily perform tasks such as filling out surveys, accepting legal agreements, or accessing new tutorials. - - Some key features of broadcasted notifications include: - - They are not associated with a specific user, so they cannot be deleted or marked as read. - - They can be scheduled to be displayed in the future or to expire after a certain time. - - By default, broadcasted notifications are published immediately and expire six months after publication. - - Only admins can create, edit, reschedule, or expire broadcasted notifications as needed. - """ - return service.broadcast(sender_context=trans, payload=payload) +@router.get( + "/api/notifications/status", + summary="Returns the current status summary of the user's notifications since a particular date.", +) +def get_notifications_status( + trans: ProvidesUserContext = DependsOnTrans, + since: OffsetNaiveDatetime = Query(), + service: NotificationService = depends(NotificationService), +) -> NotificationStatusSummary: + """Anonymous users cannot receive personal notifications, only broadcasted notifications.""" + return service.get_notifications_status(trans, since) + + +@router.get( + "/api/notifications/preferences", + summary="Returns the current user's preferences for notifications.", +) +def get_notification_preferences( + trans: ProvidesUserContext = DependsOnTrans, + service: NotificationService = depends(NotificationService), +) -> UserNotificationPreferences: + """Anonymous users cannot have notification preferences. They will receive only broadcasted notifications.""" + return service.get_user_notification_preferences(trans) + + +@router.put( + "/api/notifications/preferences", + summary="Updates the user's preferences for notifications.", +) +def update_notification_preferences( + trans: ProvidesUserContext = DependsOnTrans, + payload: UpdateUserNotificationPreferencesRequest = Body(), + service: NotificationService = depends(NotificationService), +) -> UserNotificationPreferences: + """Anonymous users cannot have notification preferences. They will receive only broadcasted notifications. + + - Can be used to completely enable/disable notifications for a particular type (category) + or to enable/disable a particular channel on each category. + """ + return service.update_user_notification_preferences(trans, payload) + + +@router.get( + "/api/notifications", + summary="Returns the list of notifications associated with the user.", +) +def get_user_notifications( + trans: ProvidesUserContext = DependsOnTrans, + limit: Optional[int] = 20, + offset: Optional[int] = None, + service: NotificationService = depends(NotificationService), +) -> UserNotificationListResponse: + """Anonymous users cannot receive personal notifications, only broadcasted notifications. + + You can use the `limit` and `offset` parameters to paginate through the notifications. + """ + return service.get_user_notifications(trans, limit=limit, offset=offset) + + +@router.get( + "/api/notifications/broadcast/{notification_id}", + summary="Returns the information of a specific broadcasted notification.", +) +def get_broadcasted( + trans: ProvidesUserContext = DependsOnTrans, + notification_id: DecodedDatabaseIdField = Path(), + service: NotificationService = depends(NotificationService), +) -> BroadcastNotificationResponse: + """Only Admin users can access inactive notifications (scheduled or recently expired).""" + return service.get_broadcasted_notification(trans, notification_id) + + +@router.get( + "/api/notifications/broadcast", + summary="Returns all currently active broadcasted notifications.", +) +def get_all_broadcasted( + trans: ProvidesUserContext = DependsOnTrans, + service: NotificationService = depends(NotificationService), +) -> BroadcastNotificationListResponse: + """Only Admin users can access inactive notifications (scheduled or recently expired).""" + return service.get_all_broadcasted_notifications(trans) + + +@router.get( + "/api/notifications/{notification_id}", + summary="Displays information about a notification received by the user.", +) +def show_notification( + trans: ProvidesUserContext = DependsOnTrans, + notification_id: DecodedDatabaseIdField = Path(), + service: NotificationService = depends(NotificationService), +) -> UserNotificationResponse: + user = service.get_authenticated_user(trans) + return service.get_user_notification(user, notification_id) + + +@router.put( + "/api/notifications/broadcast/{notification_id}", + summary="Updates the state of a broadcasted notification.", + require_admin=True, + status_code=status.HTTP_204_NO_CONTENT, +) +def update_broadcasted_notification( + trans: ProvidesUserContext = DependsOnTrans, + notification_id: DecodedDatabaseIdField = Path(), + payload: NotificationBroadcastUpdateRequest = Body(), + service: NotificationService = depends(NotificationService), +): + """Only Admins can update broadcasted notifications. This is useful to reschedule, edit or expire broadcasted notifications.""" + service.update_broadcasted_notification(trans, notification_id, payload) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.put( + "/api/notifications/{notification_id}", + summary="Updates the state of a notification received by the user.", + status_code=status.HTTP_204_NO_CONTENT, +) +def update_user_notification( + trans: ProvidesUserContext = DependsOnTrans, + notification_id: DecodedDatabaseIdField = Path(), + payload: UserNotificationUpdateRequest = Body(), + service: NotificationService = depends(NotificationService), +): + service.update_user_notification(trans, notification_id, payload) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.put( + "/api/notifications", + summary="Updates a list of notifications with the requested values in a single request.", +) +def update_user_notifications( + trans: ProvidesUserContext = DependsOnTrans, + payload: UserNotificationsBatchUpdateRequest = Body(), + service: NotificationService = depends(NotificationService), +) -> NotificationsBatchUpdateResponse: + return service.update_user_notifications(trans, set(payload.notification_ids), payload.changes) + + +@router.delete( + "/api/notifications/{notification_id}", + summary="Deletes a notification received by the user.", + status_code=status.HTTP_204_NO_CONTENT, +) +def delete_user_notification( + trans: ProvidesUserContext = DependsOnTrans, + notification_id: DecodedDatabaseIdField = Path(), + service: NotificationService = depends(NotificationService), +): + """When a notification is deleted, it is not immediately removed from the database, but marked as deleted. + + - It will not be returned in the list of notifications, but admins can still access it as long as it is not expired. + - It will be eventually removed from the database by a background task after the expiration time. + - Deleted notifications will be permanently deleted when the expiration time is reached. + """ + delete_request = UserNotificationUpdateRequest(deleted=True) + service.update_user_notification(trans, notification_id, delete_request) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.delete( + "/api/notifications", + summary="Deletes a list of notifications received by the user in a single request.", +) +def delete_user_notifications( + trans: ProvidesUserContext = DependsOnTrans, + payload: NotificationsBatchRequest = Body(), + service: NotificationService = depends(NotificationService), +) -> NotificationsBatchUpdateResponse: + delete_request = UserNotificationUpdateRequest(deleted=True) + return service.update_user_notifications(trans, set(payload.notification_ids), delete_request) + + +@router.post( + "/api/notifications", + summary="Sends a notification to a list of recipients (users, groups or roles).", + require_admin=True, +) +def send_notification( + trans: ProvidesUserContext = DependsOnTrans, + payload: NotificationCreateRequest = Body(), + service: NotificationService = depends(NotificationService), +) -> NotificationCreatedResponse: + """Sends a notification to a list of recipients (users, groups or roles).""" + return service.send_notification(sender_context=trans, payload=payload) + + +@router.post( + "/api/notifications/broadcast", + summary="Broadcasts a notification to every user in the system.", + require_admin=True, +) +def broadcast_notification( + trans: ProvidesUserContext = DependsOnTrans, + payload: BroadcastNotificationCreateRequest = Body(), + service: NotificationService = depends(NotificationService), +) -> NotificationCreatedResponse: + """Broadcasted notifications are a special kind of notification that are always accessible to all users, including anonymous users. + They are typically used to display important information such as maintenance windows or new features. + These notifications are displayed differently from regular notifications, usually in a banner at the top or bottom of the page. + + Broadcasted notifications can include action links that are displayed as buttons. + This allows users to easily perform tasks such as filling out surveys, accepting legal agreements, or accessing new tutorials. + + Some key features of broadcasted notifications include: + - They are not associated with a specific user, so they cannot be deleted or marked as read. + - They can be scheduled to be displayed in the future or to expire after a certain time. + - By default, broadcasted notifications are published immediately and expire six months after publication. + - Only admins can create, edit, reschedule, or expire broadcasted notifications as needed. + """ + return service.broadcast(sender_context=trans, payload=payload) diff --git a/lib/galaxy/webapps/galaxy/api/object_store.py b/lib/galaxy/webapps/galaxy/api/object_store.py index 6df997236a7f..6b56530c7010 100644 --- a/lib/galaxy/webapps/galaxy/api/object_store.py +++ b/lib/galaxy/webapps/galaxy/api/object_store.py @@ -39,33 +39,33 @@ ) -class FastAPIObjectStore: - @router.get( - "/api/object_stores", - summary="Get a list of (currently only concrete) object stores configured with this Galaxy instance.", - response_description="A list of the configured object stores.", - ) - def index( - trans: ProvidesUserContext = DependsOnTrans, - selectable: bool = SelectableQueryParam, - object_store: BaseObjectStore = depends(BaseObjectStore), - ) -> List[ConcreteObjectStoreModel]: - if not selectable: - raise RequestParameterInvalidException( - "The object store index query currently needs to be called with selectable=true" - ) - selectable_ids = object_store.object_store_ids_allowing_selection() - return [_model_for(selectable_id, object_store) for selectable_id in selectable_ids] +@router.get( + "/api/object_stores", + summary="Get a list of (currently only concrete) object stores configured with this Galaxy instance.", + response_description="A list of the configured object stores.", +) +def index( + trans: ProvidesUserContext = DependsOnTrans, + selectable: bool = SelectableQueryParam, + object_store: BaseObjectStore = depends(BaseObjectStore), +) -> List[ConcreteObjectStoreModel]: + if not selectable: + raise RequestParameterInvalidException( + "The object store index query currently needs to be called with selectable=true" + ) + selectable_ids = object_store.object_store_ids_allowing_selection() + return [_model_for(selectable_id, object_store) for selectable_id in selectable_ids] + - @router.get( - "/api/object_stores/{object_store_id}", - summary="Get information about a concrete object store configured with Galaxy.", - ) - def show_info( - trans: ProvidesUserContext = DependsOnTrans, - object_store_id: str = ConcreteObjectStoreIdPathParam, - ) -> ConcreteObjectStoreModel: - return _model_for(object_store_id, object_store) +@router.get( + "/api/object_stores/{object_store_id}", + summary="Get information about a concrete object store configured with Galaxy.", +) +def show_info( + trans: ProvidesUserContext = DependsOnTrans, + object_store_id: str = ConcreteObjectStoreIdPathParam, +) -> ConcreteObjectStoreModel: + return _model_for(object_store_id, object_store) def _model_for( diff --git a/lib/galaxy/webapps/galaxy/api/pages.py b/lib/galaxy/webapps/galaxy/api/pages.py index bc1ab4467c34..327a4d884022 100644 --- a/lib/galaxy/webapps/galaxy/api/pages.py +++ b/lib/galaxy/webapps/galaxy/api/pages.py @@ -94,211 +94,222 @@ ) -class FastAPIPages: - @router.get( - "/api/pages", - summary="Lists all Pages viewable by the user.", - response_description="A list with summary page information.", - ) - async def index( - response: Response, - trans: ProvidesUserContext = DependsOnTrans, - deleted: bool = DeletedQueryParam, - user_id: Optional[DecodedDatabaseIdField] = UserIdQueryParam, - show_published: bool = ShowPublishedQueryParam, - show_shared: bool = ShowSharedQueryParam, - sort_by: PageSortByEnum = SortByQueryParam, - sort_desc: bool = SortDescQueryParam, - limit: int = LimitQueryParam, - offset: int = OffsetQueryParam, - search: Optional[str] = SearchQueryParam, - service: PagesService = depends(PagesService), - ) -> PageSummaryList: - """Get a list with summary information of all Pages available to the user.""" - payload = PageIndexQueryPayload.construct( - deleted=deleted, - user_id=user_id, - show_published=show_published, - show_shared=show_shared, - sort_by=sort_by, - sort_desc=sort_desc, - limit=limit, - offset=offset, - search=search, - ) - pages, total_matches = service.index(trans, payload, include_total_count=True) - response.headers["total_matches"] = str(total_matches) - return pages - - @router.post( - "/api/pages", - summary="Create a page and return summary information.", - response_description="The page summary information.", - ) - def create( - trans: ProvidesUserContext = DependsOnTrans, - payload: CreatePagePayload = Body(...), - service: PagesService = depends(PagesService), - ) -> PageSummary: - """Get a list with details of all Pages available to the user.""" - return service.create(trans, payload) - - @router.delete( - "/api/pages/{id}", - summary="Marks the specific Page as deleted.", - status_code=status.HTTP_204_NO_CONTENT, +@router.get( + "/api/pages", + summary="Lists all Pages viewable by the user.", + response_description="A list with summary page information.", +) +async def index( + response: Response, + trans: ProvidesUserContext = DependsOnTrans, + deleted: bool = DeletedQueryParam, + user_id: Optional[DecodedDatabaseIdField] = UserIdQueryParam, + show_published: bool = ShowPublishedQueryParam, + show_shared: bool = ShowSharedQueryParam, + sort_by: PageSortByEnum = SortByQueryParam, + sort_desc: bool = SortDescQueryParam, + limit: int = LimitQueryParam, + offset: int = OffsetQueryParam, + search: Optional[str] = SearchQueryParam, + service: PagesService = depends(PagesService), +) -> PageSummaryList: + """Get a list with summary information of all Pages available to the user.""" + payload = PageIndexQueryPayload.construct( + deleted=deleted, + user_id=user_id, + show_published=show_published, + show_shared=show_shared, + sort_by=sort_by, + sort_desc=sort_desc, + limit=limit, + offset=offset, + search=search, ) - async def delete( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ): - """Marks the Page with the given ID as deleted.""" - service.delete(trans, id) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.get( - "/api/pages/{id}.pdf", - summary="Return a PDF document of the last revision of the Page.", - response_class=StreamingResponse, - responses={ - 200: { - "description": "PDF document with the last revision of the page.", - "content": {"application/pdf": {}}, - }, - 501: {"description": "PDF conversion service not available."}, + pages, total_matches = service.index(trans, payload, include_total_count=True) + response.headers["total_matches"] = str(total_matches) + return pages + + +@router.post( + "/api/pages", + summary="Create a page and return summary information.", + response_description="The page summary information.", +) +def create( + trans: ProvidesUserContext = DependsOnTrans, + payload: CreatePagePayload = Body(...), + service: PagesService = depends(PagesService), +) -> PageSummary: + """Get a list with details of all Pages available to the user.""" + return service.create(trans, payload) + + +@router.delete( + "/api/pages/{id}", + summary="Marks the specific Page as deleted.", + status_code=status.HTTP_204_NO_CONTENT, +) +async def delete( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +): + """Marks the Page with the given ID as deleted.""" + service.delete(trans, id) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.get( + "/api/pages/{id}.pdf", + summary="Return a PDF document of the last revision of the Page.", + response_class=StreamingResponse, + responses={ + 200: { + "description": "PDF document with the last revision of the page.", + "content": {"application/pdf": {}}, }, - ) - async def show_pdf( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ): - """Return a PDF document of the last revision of the Page. - - This feature may not be available in this Galaxy. - """ - pdf_bytes = service.show_pdf(trans, id) - return StreamingResponse(io.BytesIO(pdf_bytes), media_type="application/pdf") - - @router.post( - "/api/pages/{id}/prepare_download", - summary="Return a PDF document of the last revision of the Page.", - responses={ - 200: { - "description": "Short term storage reference for async monitoring of this download.", - }, - 501: {"description": "PDF conversion service not available."}, + 501: {"description": "PDF conversion service not available."}, + }, +) +async def show_pdf( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +): + """Return a PDF document of the last revision of the Page. + + This feature may not be available in this Galaxy. + """ + pdf_bytes = service.show_pdf(trans, id) + return StreamingResponse(io.BytesIO(pdf_bytes), media_type="application/pdf") + + +@router.post( + "/api/pages/{id}/prepare_download", + summary="Return a PDF document of the last revision of the Page.", + responses={ + 200: { + "description": "Short term storage reference for async monitoring of this download.", }, - ) - async def prepare_pdf( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> AsyncFile: - """Return a STS download link for this page to be downloaded as a PDF. - - This feature may not be available in this Galaxy. - """ - return service.prepare_pdf(trans, id) - - @router.get( - "/api/pages/{id}", - summary="Return a page summary and the content of the last revision.", - response_description="The page summary information.", - ) - async def show( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> PageDetails: - """Return summary information about a specific Page and the content of the last revision.""" - return service.show(trans, id) - - @router.get( - "/api/pages/{id}/sharing", - summary="Get the current sharing status of the given Page.", - ) - def sharing( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> SharingStatus: - """Return the sharing status of the item.""" - return service.shareable_service.sharing(trans, id) - - @router.put( - "/api/pages/{id}/enable_link_access", - summary="Makes this item accessible by a URL link.", - ) - def enable_link_access( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> SharingStatus: - """Makes this item accessible by a URL link and return the current sharing status.""" - return service.shareable_service.enable_link_access(trans, id) - - @router.put( - "/api/pages/{id}/disable_link_access", - summary="Makes this item inaccessible by a URL link.", - ) - def disable_link_access( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> SharingStatus: - """Makes this item inaccessible by a URL link and return the current sharing status.""" - return service.shareable_service.disable_link_access(trans, id) - - @router.put( - "/api/pages/{id}/publish", - summary="Makes this item public and accessible by a URL link.", - ) - def publish( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> SharingStatus: - """Makes this item publicly available by a URL link and return the current sharing status.""" - return service.shareable_service.publish(trans, id) - - @router.put( - "/api/pages/{id}/unpublish", - summary="Removes this item from the published list.", - ) - def unpublish( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - service: PagesService = depends(PagesService), - ) -> SharingStatus: - """Removes this item from the published list and return the current sharing status.""" - return service.shareable_service.unpublish(trans, id) - - @router.put( - "/api/pages/{id}/share_with_users", - summary="Share this item with specific users.", - ) - def share_with_users( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - payload: ShareWithPayload = Body(...), - service: PagesService = depends(PagesService), - ) -> ShareWithStatus: - """Shares this item with specific users and return the current sharing status.""" - return service.shareable_service.share_with_users(trans, id, payload) - - @router.put( - "/api/pages/{id}/slug", - summary="Set a new slug for this shared item.", - status_code=status.HTTP_204_NO_CONTENT, - ) - def set_slug( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = PageIdPathParam, - payload: SetSlugPayload = Body(...), - service: PagesService = depends(PagesService), - ): - """Sets a new slug to access this item by URL. The new slug must be unique.""" - service.shareable_service.set_slug(trans, id, payload) - return Response(status_code=status.HTTP_204_NO_CONTENT) + 501: {"description": "PDF conversion service not available."}, + }, +) +async def prepare_pdf( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> AsyncFile: + """Return a STS download link for this page to be downloaded as a PDF. + + This feature may not be available in this Galaxy. + """ + return service.prepare_pdf(trans, id) + + +@router.get( + "/api/pages/{id}", + summary="Return a page summary and the content of the last revision.", + response_description="The page summary information.", +) +async def show( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> PageDetails: + """Return summary information about a specific Page and the content of the last revision.""" + return service.show(trans, id) + + +@router.get( + "/api/pages/{id}/sharing", + summary="Get the current sharing status of the given Page.", +) +def sharing( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> SharingStatus: + """Return the sharing status of the item.""" + return service.shareable_service.sharing(trans, id) + + +@router.put( + "/api/pages/{id}/enable_link_access", + summary="Makes this item accessible by a URL link.", +) +def enable_link_access( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> SharingStatus: + """Makes this item accessible by a URL link and return the current sharing status.""" + return service.shareable_service.enable_link_access(trans, id) + + +@router.put( + "/api/pages/{id}/disable_link_access", + summary="Makes this item inaccessible by a URL link.", +) +def disable_link_access( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> SharingStatus: + """Makes this item inaccessible by a URL link and return the current sharing status.""" + return service.shareable_service.disable_link_access(trans, id) + + +@router.put( + "/api/pages/{id}/publish", + summary="Makes this item public and accessible by a URL link.", +) +def publish( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> SharingStatus: + """Makes this item publicly available by a URL link and return the current sharing status.""" + return service.shareable_service.publish(trans, id) + + +@router.put( + "/api/pages/{id}/unpublish", + summary="Removes this item from the published list.", +) +def unpublish( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + service: PagesService = depends(PagesService), +) -> SharingStatus: + """Removes this item from the published list and return the current sharing status.""" + return service.shareable_service.unpublish(trans, id) + + +@router.put( + "/api/pages/{id}/share_with_users", + summary="Share this item with specific users.", +) +def share_with_users( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + payload: ShareWithPayload = Body(...), + service: PagesService = depends(PagesService), +) -> ShareWithStatus: + """Shares this item with specific users and return the current sharing status.""" + return service.shareable_service.share_with_users(trans, id, payload) + + +@router.put( + "/api/pages/{id}/slug", + summary="Set a new slug for this shared item.", + status_code=status.HTTP_204_NO_CONTENT, +) +def set_slug( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = PageIdPathParam, + payload: SetSlugPayload = Body(...), + service: PagesService = depends(PagesService), +): + """Sets a new slug to access this item by URL. The new slug must be unique.""" + service.shareable_service.set_slug(trans, id, payload) + return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/lib/galaxy/webapps/galaxy/api/quotas.py b/lib/galaxy/webapps/galaxy/api/quotas.py index 0971052d96c9..a6dcc2bab5ee 100644 --- a/lib/galaxy/webapps/galaxy/api/quotas.py +++ b/lib/galaxy/webapps/galaxy/api/quotas.py @@ -34,121 +34,128 @@ ) -class FastAPIQuota: - @router.get( - "/api/quotas", - summary="Displays a list with information of quotas that are currently active.", - require_admin=True, - ) - def index( - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> QuotaSummaryList: - """Displays a list with information of quotas that are currently active.""" - return service.index(trans) - - @router.get( - "/api/quotas/deleted", - summary="Displays a list with information of quotas that have been deleted.", - require_admin=True, - ) - def index_deleted( - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> QuotaSummaryList: - """Displays a list with information of quotas that have been deleted.""" - return service.index(trans, deleted=True) - - @router.get( - "/api/quotas/{id}", - name="quota", - summary="Displays details on a particular active quota.", - require_admin=True, - ) - def show( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = QuotaIdPathParam, - service: QuotasService = depends(QuotasService), - ) -> QuotaDetails: - """Displays details on a particular active quota.""" - return service.show(trans, id) - - @router.get( - "/api/quotas/deleted/{id}", - name="deleted_quota", - summary="Displays details on a particular quota that has been deleted.", - require_admin=True, - ) - def show_deleted( - trans: ProvidesUserContext = DependsOnTrans, - id: DecodedDatabaseIdField = QuotaIdPathParam, - service: QuotasService = depends(QuotasService), - ) -> QuotaDetails: - """Displays details on a particular quota that has been deleted.""" - return service.show(trans, id, deleted=True) - - @router.post( - "/api/quotas", - summary="Creates a new quota.", - require_admin=True, - ) - def create( - payload: CreateQuotaParams, - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> CreateQuotaResult: - """Creates a new quota.""" - return service.create(trans, payload) - - @router.put( - "/api/quotas/{id}", - summary="Updates an existing quota.", - require_admin=True, - ) - def update( - payload: UpdateQuotaParams, - id: DecodedDatabaseIdField = QuotaIdPathParam, - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> str: - """Updates an existing quota.""" - return service.update(trans, id, payload) - - @router.delete( - "/api/quotas/{id}", - summary="Deletes an existing quota.", - require_admin=True, - ) - def delete( - id: DecodedDatabaseIdField = QuotaIdPathParam, - trans: ProvidesUserContext = DependsOnTrans, - payload: DeleteQuotaPayload = Body(None), # Optional - service: QuotasService = depends(QuotasService), - ) -> str: - """Deletes an existing quota.""" - return service.delete(trans, id, payload) - - @router.post( - "/api/quotas/{id}/purge", - summary="Purges a previously deleted quota.", - require_admin=True, - ) - def purge( - id: DecodedDatabaseIdField = QuotaIdPathParam, - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> str: - return service.purge(trans, id) - - @router.post( - "/api/quotas/deleted/{id}/undelete", - summary="Restores a previously deleted quota.", - require_admin=True, - ) - def undelete( - id: DecodedDatabaseIdField = QuotaIdPathParam, - trans: ProvidesUserContext = DependsOnTrans, - service: QuotasService = depends(QuotasService), - ) -> str: - """Restores a previously deleted quota.""" - return service.undelete(trans, id) +@router.get( + "/api/quotas", + summary="Displays a list with information of quotas that are currently active.", + require_admin=True, +) +def index( + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> QuotaSummaryList: + """Displays a list with information of quotas that are currently active.""" + return service.index(trans) + + +@router.get( + "/api/quotas/deleted", + summary="Displays a list with information of quotas that have been deleted.", + require_admin=True, +) +def index_deleted( + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> QuotaSummaryList: + """Displays a list with information of quotas that have been deleted.""" + return service.index(trans, deleted=True) + + +@router.get( + "/api/quotas/{id}", + name="quota", + summary="Displays details on a particular active quota.", + require_admin=True, +) +def show( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = QuotaIdPathParam, + service: QuotasService = depends(QuotasService), +) -> QuotaDetails: + """Displays details on a particular active quota.""" + return service.show(trans, id) + + +@router.get( + "/api/quotas/deleted/{id}", + name="deleted_quota", + summary="Displays details on a particular quota that has been deleted.", + require_admin=True, +) +def show_deleted( + trans: ProvidesUserContext = DependsOnTrans, + id: DecodedDatabaseIdField = QuotaIdPathParam, + service: QuotasService = depends(QuotasService), +) -> QuotaDetails: + """Displays details on a particular quota that has been deleted.""" + return service.show(trans, id, deleted=True) + + +@router.post( + "/api/quotas", + summary="Creates a new quota.", + require_admin=True, +) +def create( + payload: CreateQuotaParams, + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> CreateQuotaResult: + """Creates a new quota.""" + return service.create(trans, payload) + + +@router.put( + "/api/quotas/{id}", + summary="Updates an existing quota.", + require_admin=True, +) +def update( + payload: UpdateQuotaParams, + id: DecodedDatabaseIdField = QuotaIdPathParam, + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> str: + """Updates an existing quota.""" + return service.update(trans, id, payload) + + +@router.delete( + "/api/quotas/{id}", + summary="Deletes an existing quota.", + require_admin=True, +) +def delete( + id: DecodedDatabaseIdField = QuotaIdPathParam, + trans: ProvidesUserContext = DependsOnTrans, + payload: DeleteQuotaPayload = Body(None), # Optional + service: QuotasService = depends(QuotasService), +) -> str: + """Deletes an existing quota.""" + return service.delete(trans, id, payload) + + +@router.post( + "/api/quotas/{id}/purge", + summary="Purges a previously deleted quota.", + require_admin=True, +) +def purge( + id: DecodedDatabaseIdField = QuotaIdPathParam, + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> str: + return service.purge(trans, id) + + +@router.post( + "/api/quotas/deleted/{id}/undelete", + summary="Restores a previously deleted quota.", + require_admin=True, +) +def undelete( + id: DecodedDatabaseIdField = QuotaIdPathParam, + trans: ProvidesUserContext = DependsOnTrans, + service: QuotasService = depends(QuotasService), +) -> str: + """Restores a previously deleted quota.""" + return service.undelete(trans, id) diff --git a/lib/galaxy/webapps/galaxy/api/remote_files.py b/lib/galaxy/webapps/galaxy/api/remote_files.py index 1e712a60316d..0a59146cdf09 100644 --- a/lib/galaxy/webapps/galaxy/api/remote_files.py +++ b/lib/galaxy/webapps/galaxy/api/remote_files.py @@ -102,61 +102,62 @@ ) -class FastAPIRemoteFiles: - @router.get( - "/api/remote_files", - summary="Displays remote files available to the user.", - response_description="A list with details about the remote files available to the user.", - ) - @router.get( - "/api/ftp_files", - deprecated=True, - summary="Displays remote files available to the user. Please use /api/remote_files instead.", - ) - async def index( - user_ctx: ProvidesUserContext = DependsOnTrans, - target: str = TargetQueryParam, - format: Optional[RemoteFilesFormat] = FormatQueryParam, - recursive: Optional[bool] = RecursiveQueryParam, - disable: Optional[RemoteFilesDisableMode] = DisableModeQueryParam, - writeable: Optional[bool] = WriteableQueryParam, - manager: RemoteFilesManager = depends(RemoteFilesManager), - ) -> AnyRemoteFilesListResponse: - """Lists all remote files available to the user from different sources.""" - return manager.index(user_ctx, target, format, recursive, disable, writeable) - - @router.get( - "/api/remote_files/plugins", - summary="Display plugin information for each of the gxfiles:// URI targets available.", - response_description="A list with details about each plugin.", - ) - async def plugins( - user_ctx: ProvidesUserContext = DependsOnTrans, - browsable_only: Optional[bool] = BrowsableQueryParam, - include_kind: Annotated[Optional[List[PluginKind]], IncludeKindQueryParam] = None, - exclude_kind: Annotated[Optional[List[PluginKind]], ExcludeKindQueryParam] = None, - manager: RemoteFilesManager = depends(RemoteFilesManager), - ) -> FilesSourcePluginList: - """Display plugin information for each of the gxfiles:// URI targets available.""" - return manager.get_files_source_plugins( - user_ctx, - browsable_only, - set(include_kind) if include_kind else None, - set(exclude_kind) if exclude_kind else None, - ) - - @router.post( - "/api/remote_files", - summary="Creates a new entry (directory/record) on the remote files source.", +@router.get( + "/api/remote_files", + summary="Displays remote files available to the user.", + response_description="A list with details about the remote files available to the user.", +) +@router.get( + "/api/ftp_files", + deprecated=True, + summary="Displays remote files available to the user. Please use /api/remote_files instead.", +) +async def index( + user_ctx: ProvidesUserContext = DependsOnTrans, + target: str = TargetQueryParam, + format: Optional[RemoteFilesFormat] = FormatQueryParam, + recursive: Optional[bool] = RecursiveQueryParam, + disable: Optional[RemoteFilesDisableMode] = DisableModeQueryParam, + writeable: Optional[bool] = WriteableQueryParam, + manager: RemoteFilesManager = depends(RemoteFilesManager), +) -> AnyRemoteFilesListResponse: + """Lists all remote files available to the user from different sources.""" + return manager.index(user_ctx, target, format, recursive, disable, writeable) + + +@router.get( + "/api/remote_files/plugins", + summary="Display plugin information for each of the gxfiles:// URI targets available.", + response_description="A list with details about each plugin.", +) +async def plugins( + user_ctx: ProvidesUserContext = DependsOnTrans, + browsable_only: Optional[bool] = BrowsableQueryParam, + include_kind: Annotated[Optional[List[PluginKind]], IncludeKindQueryParam] = None, + exclude_kind: Annotated[Optional[List[PluginKind]], ExcludeKindQueryParam] = None, + manager: RemoteFilesManager = depends(RemoteFilesManager), +) -> FilesSourcePluginList: + """Display plugin information for each of the gxfiles:// URI targets available.""" + return manager.get_files_source_plugins( + user_ctx, + browsable_only, + set(include_kind) if include_kind else None, + set(exclude_kind) if exclude_kind else None, ) - async def create_entry( - user_ctx: ProvidesUserContext = DependsOnTrans, - payload: CreateEntryPayload = Body( - ..., - title="Entry Data", - description="Information about the entry to create. Depends on the target file source.", - ), - manager: RemoteFilesManager = depends(RemoteFilesManager), - ) -> CreatedEntryResponse: - """Creates a new entry on the remote files source.""" - return manager.create_entry(user_ctx, payload) + + +@router.post( + "/api/remote_files", + summary="Creates a new entry (directory/record) on the remote files source.", +) +async def create_entry( + user_ctx: ProvidesUserContext = DependsOnTrans, + payload: CreateEntryPayload = Body( + ..., + title="Entry Data", + description="Information about the entry to create. Depends on the target file source.", + ), + manager: RemoteFilesManager = depends(RemoteFilesManager), +) -> CreatedEntryResponse: + """Creates a new entry on the remote files source.""" + return manager.create_entry(user_ctx, payload) diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index fdbdd908f68d..2c575562a832 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -35,58 +35,62 @@ def role_to_model(role): return RoleModelResponse(**item) -class FastAPIRoles: - @router.get("/api/roles") - def index( - trans: ProvidesUserContext = DependsOnTrans, role_manager: RoleManager = depends(RoleManager) - ) -> RoleListResponse: - roles = role_manager.list_displayable_roles(trans) - return RoleListResponse(__root__=[role_to_model(r) for r in roles]) - - @router.get("/api/roles/{id}") - def show( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - role_manager: RoleManager = depends(RoleManager), - ) -> RoleModelResponse: - role = role_manager.get(trans, id) - return role_to_model(role) - - @router.post("/api/roles", require_admin=True) - def create( - trans: ProvidesUserContext = DependsOnTrans, - role_definition_model: RoleDefinitionModel = Body(...), - role_manager: RoleManager = depends(RoleManager), - ) -> RoleModelResponse: - role = role_manager.create_role(trans, role_definition_model) - return role_to_model(role) - - @router.delete("/api/roles/{id}", require_admin=True) - def delete( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - role_manager: RoleManager = depends(RoleManager), - ) -> RoleModelResponse: - role = role_manager.get(trans, id) - role = role_manager.delete(trans, role) - return role_to_model(role) - - @router.post("/api/roles/{id}/purge", require_admin=True) - def purge( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - role_manager: RoleManager = depends(RoleManager), - ) -> RoleModelResponse: - role = role_manager.get(trans, id) - role = role_manager.purge(trans, role) - return role_to_model(role) - - @router.post("/api/roles/{id}/undelete", require_admin=True) - def undelete( - id: DecodedDatabaseIdField, - trans: ProvidesUserContext = DependsOnTrans, - role_manager: RoleManager = depends(RoleManager), - ) -> RoleModelResponse: - role = role_manager.get(trans, id) - role = role_manager.undelete(trans, role) - return role_to_model(role) +@router.get("/api/roles") +def index( + trans: ProvidesUserContext = DependsOnTrans, role_manager: RoleManager = depends(RoleManager) +) -> RoleListResponse: + roles = role_manager.list_displayable_roles(trans) + return RoleListResponse(__root__=[role_to_model(r) for r in roles]) + + +@router.get("/api/roles/{id}") +def show( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + role_manager: RoleManager = depends(RoleManager), +) -> RoleModelResponse: + role = role_manager.get(trans, id) + return role_to_model(role) + + +@router.post("/api/roles", require_admin=True) +def create( + trans: ProvidesUserContext = DependsOnTrans, + role_definition_model: RoleDefinitionModel = Body(...), + role_manager: RoleManager = depends(RoleManager), +) -> RoleModelResponse: + role = role_manager.create_role(trans, role_definition_model) + return role_to_model(role) + + +@router.delete("/api/roles/{id}", require_admin=True) +def delete( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + role_manager: RoleManager = depends(RoleManager), +) -> RoleModelResponse: + role = role_manager.get(trans, id) + role = role_manager.delete(trans, role) + return role_to_model(role) + + +@router.post("/api/roles/{id}/purge", require_admin=True) +def purge( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + role_manager: RoleManager = depends(RoleManager), +) -> RoleModelResponse: + role = role_manager.get(trans, id) + role = role_manager.purge(trans, role) + return role_to_model(role) + + +@router.post("/api/roles/{id}/undelete", require_admin=True) +def undelete( + id: DecodedDatabaseIdField, + trans: ProvidesUserContext = DependsOnTrans, + role_manager: RoleManager = depends(RoleManager), +) -> RoleModelResponse: + role = role_manager.get(trans, id) + role = role_manager.undelete(trans, role) + return role_to_model(role) diff --git a/lib/galaxy/webapps/galaxy/api/storage_cleaner.py b/lib/galaxy/webapps/galaxy/api/storage_cleaner.py index 396bbe5501ca..ab7f1cb6615d 100644 --- a/lib/galaxy/webapps/galaxy/api/storage_cleaner.py +++ b/lib/galaxy/webapps/galaxy/api/storage_cleaner.py @@ -46,100 +46,106 @@ ) -class FastAPIStorageCleaner: - @router.get( - "/api/storage/histories/discarded/summary", - summary="Returns information with the total storage space taken by discarded histories associated with the given user.", - ) - def discarded_histories_summary( - trans: ProvidesHistoryContext = DependsOnTrans, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> CleanableItemsSummary: - return service.get_discarded_summary(trans, stored_item_type="history") - - @router.get( - "/api/storage/histories/discarded", - summary="Returns all discarded histories associated with the given user.", - ) - def discarded_histories( - trans: ProvidesHistoryContext = DependsOnTrans, - offset: Optional[int] = OffsetQueryParam, - limit: Optional[int] = LimitQueryParam, - order: Optional[StoredItemOrderBy] = OrderQueryParam, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> List[StoredItem]: - return service.get_discarded(trans, "history", offset, limit, order) - - @router.delete( - "/api/storage/histories", - summary="Purges a set of histories by ID. The histories must be owned by the user.", - ) - def cleanup_histories( - trans: ProvidesHistoryContext = DependsOnTrans, - payload: CleanupStorageItemsRequest = Body(...), - service: StorageCleanerService = depends(StorageCleanerService), - ) -> StorageItemsCleanupResult: - """ - **Warning**: This operation cannot be undone. All objects will be deleted permanently from the disk. - """ - return service.cleanup_items(trans, stored_item_type="history", item_ids=set(payload.item_ids)) - - @router.get( - "/api/storage/datasets/discarded/summary", - summary="Returns information with the total storage space taken by discarded datasets owned by the given user.", - ) - def discarded_datasets_summary( - trans: ProvidesHistoryContext = DependsOnTrans, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> CleanableItemsSummary: - return service.get_discarded_summary(trans, stored_item_type="dataset") - - @router.get( - "/api/storage/datasets/discarded", - summary="Returns discarded datasets owned by the given user. The results can be paginated.", - ) - def discarded_datasets( - trans: ProvidesHistoryContext = DependsOnTrans, - offset: Optional[int] = OffsetQueryParam, - limit: Optional[int] = LimitQueryParam, - order: Optional[StoredItemOrderBy] = OrderQueryParam, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> List[StoredItem]: - return service.get_discarded(trans, "dataset", offset, limit, order) - - @router.delete( - "/api/storage/datasets", - summary="Purges a set of datasets by ID from disk. The datasets must be owned by the user.", - ) - def cleanup_datasets( - trans: ProvidesHistoryContext = DependsOnTrans, - payload: CleanupStorageItemsRequest = Body(...), - service: StorageCleanerService = depends(StorageCleanerService), - ) -> StorageItemsCleanupResult: - """ - **Warning**: This operation cannot be undone. All objects will be deleted permanently from the disk. - """ - return service.cleanup_items(trans, stored_item_type="dataset", item_ids=set(payload.item_ids)) - - @router.get( - "/api/storage/histories/archived/summary", - summary="Returns information with the total storage space taken by non-purged archived histories associated with the given user.", - ) - def archived_histories_summary( - trans: ProvidesHistoryContext = DependsOnTrans, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> CleanableItemsSummary: - return service.get_archived_summary(trans, stored_item_type="history") - - @router.get( - "/api/storage/histories/archived", - summary="Returns archived histories owned by the given user that are not purged. The results can be paginated.", - ) - def archived_histories( - trans: ProvidesHistoryContext = DependsOnTrans, - offset: Optional[int] = OffsetQueryParam, - limit: Optional[int] = LimitQueryParam, - order: Optional[StoredItemOrderBy] = OrderQueryParam, - service: StorageCleanerService = depends(StorageCleanerService), - ) -> List[StoredItem]: - return service.get_archived(trans, "history", offset, limit, order) +@router.get( + "/api/storage/histories/discarded/summary", + summary="Returns information with the total storage space taken by discarded histories associated with the given user.", +) +def discarded_histories_summary( + trans: ProvidesHistoryContext = DependsOnTrans, + service: StorageCleanerService = depends(StorageCleanerService), +) -> CleanableItemsSummary: + return service.get_discarded_summary(trans, stored_item_type="history") + + +@router.get( + "/api/storage/histories/discarded", + summary="Returns all discarded histories associated with the given user.", +) +def discarded_histories( + trans: ProvidesHistoryContext = DependsOnTrans, + offset: Optional[int] = OffsetQueryParam, + limit: Optional[int] = LimitQueryParam, + order: Optional[StoredItemOrderBy] = OrderQueryParam, + service: StorageCleanerService = depends(StorageCleanerService), +) -> List[StoredItem]: + return service.get_discarded(trans, "history", offset, limit, order) + + +@router.delete( + "/api/storage/histories", + summary="Purges a set of histories by ID. The histories must be owned by the user.", +) +def cleanup_histories( + trans: ProvidesHistoryContext = DependsOnTrans, + payload: CleanupStorageItemsRequest = Body(...), + service: StorageCleanerService = depends(StorageCleanerService), +) -> StorageItemsCleanupResult: + """ + **Warning**: This operation cannot be undone. All objects will be deleted permanently from the disk. + """ + return service.cleanup_items(trans, stored_item_type="history", item_ids=set(payload.item_ids)) + + +@router.get( + "/api/storage/datasets/discarded/summary", + summary="Returns information with the total storage space taken by discarded datasets owned by the given user.", +) +def discarded_datasets_summary( + trans: ProvidesHistoryContext = DependsOnTrans, + service: StorageCleanerService = depends(StorageCleanerService), +) -> CleanableItemsSummary: + return service.get_discarded_summary(trans, stored_item_type="dataset") + + +@router.get( + "/api/storage/datasets/discarded", + summary="Returns discarded datasets owned by the given user. The results can be paginated.", +) +def discarded_datasets( + trans: ProvidesHistoryContext = DependsOnTrans, + offset: Optional[int] = OffsetQueryParam, + limit: Optional[int] = LimitQueryParam, + order: Optional[StoredItemOrderBy] = OrderQueryParam, + service: StorageCleanerService = depends(StorageCleanerService), +) -> List[StoredItem]: + return service.get_discarded(trans, "dataset", offset, limit, order) + + +@router.delete( + "/api/storage/datasets", + summary="Purges a set of datasets by ID from disk. The datasets must be owned by the user.", +) +def cleanup_datasets( + trans: ProvidesHistoryContext = DependsOnTrans, + payload: CleanupStorageItemsRequest = Body(...), + service: StorageCleanerService = depends(StorageCleanerService), +) -> StorageItemsCleanupResult: + """ + **Warning**: This operation cannot be undone. All objects will be deleted permanently from the disk. + """ + return service.cleanup_items(trans, stored_item_type="dataset", item_ids=set(payload.item_ids)) + + +@router.get( + "/api/storage/histories/archived/summary", + summary="Returns information with the total storage space taken by non-purged archived histories associated with the given user.", +) +def archived_histories_summary( + trans: ProvidesHistoryContext = DependsOnTrans, + service: StorageCleanerService = depends(StorageCleanerService), +) -> CleanableItemsSummary: + return service.get_archived_summary(trans, stored_item_type="history") + + +@router.get( + "/api/storage/histories/archived", + summary="Returns archived histories owned by the given user that are not purged. The results can be paginated.", +) +def archived_histories( + trans: ProvidesHistoryContext = DependsOnTrans, + offset: Optional[int] = OffsetQueryParam, + limit: Optional[int] = LimitQueryParam, + order: Optional[StoredItemOrderBy] = OrderQueryParam, + service: StorageCleanerService = depends(StorageCleanerService), +) -> List[StoredItem]: + return service.get_archived(trans, "history", offset, limit, order) diff --git a/lib/galaxy/webapps/galaxy/api/tags.py b/lib/galaxy/webapps/galaxy/api/tags.py index 3d0cf7a687a1..e51d9802ea2d 100644 --- a/lib/galaxy/webapps/galaxy/api/tags.py +++ b/lib/galaxy/webapps/galaxy/api/tags.py @@ -25,25 +25,24 @@ router = Router(tags=["tags"]) -class FastAPITags: - @router.put( - "/api/tags", - summary="Apply a new set of tags to an item.", - status_code=status.HTTP_204_NO_CONTENT, - ) - def update( - trans: ProvidesUserContext = DependsOnTrans, - payload: ItemTagsPayload = Body( - ..., # Required - title="Payload", - description="Request body containing the item and the tags to be assigned.", - ), - manager: TagsManager = depends(TagsManager), - ): - """Replaces the tags associated with an item with the new ones specified in the payload. +@router.put( + "/api/tags", + summary="Apply a new set of tags to an item.", + status_code=status.HTTP_204_NO_CONTENT, +) +def update( + trans: ProvidesUserContext = DependsOnTrans, + payload: ItemTagsPayload = Body( + ..., # Required + title="Payload", + description="Request body containing the item and the tags to be assigned.", + ), + manager: TagsManager = depends(TagsManager), +): + """Replaces the tags associated with an item with the new ones specified in the payload. - - The previous tags will be __deleted__. - - If no tags are provided in the request body, the currently associated tags will also be __deleted__. - """ - manager.update(trans, payload) - return Response(status_code=status.HTTP_204_NO_CONTENT) + - The previous tags will be __deleted__. + - If no tags are provided in the request body, the currently associated tags will also be __deleted__. + """ + manager.update(trans, payload) + return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/lib/galaxy/webapps/galaxy/api/tasks.py b/lib/galaxy/webapps/galaxy/api/tasks.py index 954268056c75..0346ccca5104 100644 --- a/lib/galaxy/webapps/galaxy/api/tasks.py +++ b/lib/galaxy/webapps/galaxy/api/tasks.py @@ -18,11 +18,10 @@ router = Router(tags=["tasks"]) -class FastAPITasks: - @router.get( - "/api/tasks/{task_id}/state", - summary="Determine state of task ID", - response_description="String indicating task state.", - ) - def state(task_id: UUID, manager: AsyncTasksManager = depends(AsyncTasksManager)) -> TaskState: # type: ignore[type-abstract] - return manager.get_state(task_id) +@router.get( + "/api/tasks/{task_id}/state", + summary="Determine state of task ID", + response_description="String indicating task state.", +) +def state(task_id: UUID, manager: AsyncTasksManager = depends(AsyncTasksManager)) -> TaskState: # type: ignore[type-abstract] + return manager.get_state(task_id) diff --git a/lib/galaxy/webapps/galaxy/api/tool_data.py b/lib/galaxy/webapps/galaxy/api/tool_data.py index c3bd99313ef0..ca3d6f4c80ff 100644 --- a/lib/galaxy/webapps/galaxy/api/tool_data.py +++ b/lib/galaxy/webapps/galaxy/api/tool_data.py @@ -46,99 +46,104 @@ class ImportToolDataBundle(BaseModel): source: ImportToolDataBundleSource = Field(..., discriminator="src") -class FastAPIToolData: - @router.get( - "/api/tool_data", - summary="Lists all available data tables", - response_description="A list with details on individual data tables.", - require_admin=False, - ) - async def index(tool_data_manager: ToolDataManager = depends(ToolDataManager)) -> ToolDataEntryList: - """Get the list of all available data tables.""" - return tool_data_manager.index() - - @router.post( - "/api/tool_data", - summary="Import a data manager bundle", - require_admin=True, - ) - async def create( - tool_data_file_path=None, import_bundle_model: ImportToolDataBundle = Body(...) - ) -> AsyncTaskResultSummary: - source = import_bundle_model.source - result = import_data_bundle.delay(tool_data_file_path=tool_data_file_path, **source.dict()) - summary = async_task_summary(result) - return summary - - @router.get( - "/api/tool_data/{table_name}", - summary="Get details of a given data table", - response_description="A description of the given data table and its content", - require_admin=True, - ) - async def show( - table_name: str = ToolDataTableName, tool_data_manager: ToolDataManager = depends(ToolDataManager) - ) -> ToolDataDetails: - """Get details of a given tool data table.""" - return tool_data_manager.show(table_name) - - @router.get( - "/api/tool_data/{table_name}/reload", - summary="Reloads a tool data table", - response_description="A description of the reloaded data table and its content", - require_admin=True, - ) - async def reload( - table_name: str = ToolDataTableName, tool_data_manager: ToolDataManager = depends(ToolDataManager) - ) -> ToolDataDetails: - """Reloads a data table and return its details.""" - return tool_data_manager.reload(table_name) - - @router.get( - "/api/tool_data/{table_name}/fields/{field_name}", - summary="Get information about a particular field in a tool data table", - response_description="Information about a data table field", - require_admin=True, - ) - async def show_field( - table_name: str = ToolDataTableName, - field_name: str = ToolDataTableFieldName, - tool_data_manager: ToolDataManager = depends(ToolDataManager), - ) -> ToolDataField: - """Reloads a data table and return its details.""" - return tool_data_manager.show_field(table_name, field_name) - - @router.get( - "/api/tool_data/{table_name}/fields/{field_name}/files/{file_name}", - summary="Get information about a particular field in a tool data table", - response_description="Information about a data table field", - response_class=GalaxyFileResponse, - require_admin=True, - ) - async def download_field_file( - table_name: str = ToolDataTableName, - field_name: str = ToolDataTableFieldName, - file_name: str = Path( - ..., # Mark this field as required - title="File name", - description="The name of a file associated with this data table field", - ), - tool_data_manager: ToolDataManager = depends(ToolDataManager), - ): - """Download a file associated with the data table field.""" - path = tool_data_manager.get_field_file_path(table_name, field_name, file_name) - return GalaxyFileResponse(str(path)) - - @router.delete( - "/api/tool_data/{table_name}", - summary="Removes an item from a data table", - response_description="A description of the affected data table and its content", - require_admin=True, - ) - async def delete( - payload: ToolDataItem, - table_name: str = ToolDataTableName, - tool_data_manager: ToolDataManager = depends(ToolDataManager), - ) -> ToolDataDetails: - """Removes an item from a data table and reloads it to return its updated details.""" - return tool_data_manager.delete(table_name, payload.values) +@router.get( + "/api/tool_data", + summary="Lists all available data tables", + response_description="A list with details on individual data tables.", + require_admin=False, +) +async def index(tool_data_manager: ToolDataManager = depends(ToolDataManager)) -> ToolDataEntryList: + """Get the list of all available data tables.""" + return tool_data_manager.index() + + +@router.post( + "/api/tool_data", + summary="Import a data manager bundle", + require_admin=True, +) +async def create( + tool_data_file_path=None, import_bundle_model: ImportToolDataBundle = Body(...) +) -> AsyncTaskResultSummary: + source = import_bundle_model.source + result = import_data_bundle.delay(tool_data_file_path=tool_data_file_path, **source.dict()) + summary = async_task_summary(result) + return summary + + +@router.get( + "/api/tool_data/{table_name}", + summary="Get details of a given data table", + response_description="A description of the given data table and its content", + require_admin=True, +) +async def show( + table_name: str = ToolDataTableName, tool_data_manager: ToolDataManager = depends(ToolDataManager) +) -> ToolDataDetails: + """Get details of a given tool data table.""" + return tool_data_manager.show(table_name) + + +@router.get( + "/api/tool_data/{table_name}/reload", + summary="Reloads a tool data table", + response_description="A description of the reloaded data table and its content", + require_admin=True, +) +async def reload( + table_name: str = ToolDataTableName, tool_data_manager: ToolDataManager = depends(ToolDataManager) +) -> ToolDataDetails: + """Reloads a data table and return its details.""" + return tool_data_manager.reload(table_name) + + +@router.get( + "/api/tool_data/{table_name}/fields/{field_name}", + summary="Get information about a particular field in a tool data table", + response_description="Information about a data table field", + require_admin=True, +) +async def show_field( + table_name: str = ToolDataTableName, + field_name: str = ToolDataTableFieldName, + tool_data_manager: ToolDataManager = depends(ToolDataManager), +) -> ToolDataField: + """Reloads a data table and return its details.""" + return tool_data_manager.show_field(table_name, field_name) + + +@router.get( + "/api/tool_data/{table_name}/fields/{field_name}/files/{file_name}", + summary="Get information about a particular field in a tool data table", + response_description="Information about a data table field", + response_class=GalaxyFileResponse, + require_admin=True, +) +async def download_field_file( + table_name: str = ToolDataTableName, + field_name: str = ToolDataTableFieldName, + file_name: str = Path( + ..., # Mark this field as required + title="File name", + description="The name of a file associated with this data table field", + ), + tool_data_manager: ToolDataManager = depends(ToolDataManager), +): + """Download a file associated with the data table field.""" + path = tool_data_manager.get_field_file_path(table_name, field_name, file_name) + return GalaxyFileResponse(str(path)) + + +@router.delete( + "/api/tool_data/{table_name}", + summary="Removes an item from a data table", + response_description="A description of the affected data table and its content", + require_admin=True, +) +async def delete( + payload: ToolDataItem, + table_name: str = ToolDataTableName, + tool_data_manager: ToolDataManager = depends(ToolDataManager), +) -> ToolDataDetails: + """Removes an item from a data table and reloads it to return its updated details.""" + return tool_data_manager.delete(table_name, payload.values) diff --git a/lib/galaxy/webapps/galaxy/api/tours.py b/lib/galaxy/webapps/galaxy/api/tours.py index e63ce603e0a7..9a88f8585396 100644 --- a/lib/galaxy/webapps/galaxy/api/tours.py +++ b/lib/galaxy/webapps/galaxy/api/tours.py @@ -19,18 +19,19 @@ router = Router(tags=["tours"]) -class FastAPITours: - @router.get("/api/tours") - def index(registry: ToursRegistry = depends(ToursRegistry)) -> TourList: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 - """Return list of available tours.""" - return registry.get_tours() - - @router.get("/api/tours/{tour_id}") - def show(tour_id: str, registry: ToursRegistry = depends(ToursRegistry)) -> TourDetails: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 - """Return a tour definition.""" - return registry.tour_contents(tour_id) - - @router.post("/api/tours/{tour_id}", require_admin=True) - def update_tour(tour_id: str, registry: ToursRegistry = depends(ToursRegistry)) -> TourDetails: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 - """Return a tour definition.""" - return registry.load_tour(tour_id) +@router.get("/api/tours") +def index(registry: ToursRegistry = depends(ToursRegistry)) -> TourList: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 + """Return list of available tours.""" + return registry.get_tours() + + +@router.get("/api/tours/{tour_id}") +def show(tour_id: str, registry: ToursRegistry = depends(ToursRegistry)) -> TourDetails: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 + """Return a tour definition.""" + return registry.tour_contents(tour_id) + + +@router.post("/api/tours/{tour_id}", require_admin=True) +def update_tour(tour_id: str, registry: ToursRegistry = depends(ToursRegistry)) -> TourDetails: # type: ignore[type-abstract] # https://github.com/python/mypy/issues/4717 + """Return a tour definition.""" + return registry.load_tour(tour_id) diff --git a/lib/tool_shed/webapp/api2/authenticate.py b/lib/tool_shed/webapp/api2/authenticate.py index c478870ca767..68635f766630 100644 --- a/lib/tool_shed/webapp/api2/authenticate.py +++ b/lib/tool_shed/webapp/api2/authenticate.py @@ -12,15 +12,14 @@ router = Router(tags=["authenticate"]) -class FastAPIAuthenticate: - @router.get( - "/api/authenticate/baseauth", - summary="Returns returns an API key for authenticated user based on BaseAuth headers.", - operation_id="authenticate__baseauth", - ) - def get_api_key( - request: Request, authentication_service: AuthenticationService = depends(AuthenticationService) - ) -> APIKeyResponse: - authorization = request.headers.get("Authorization") - auth = {"HTTP_AUTHORIZATION": authorization} - return authentication_service.get_api_key(auth, request) +@router.get( + "/api/authenticate/baseauth", + summary="Returns returns an API key for authenticated user based on BaseAuth headers.", + operation_id="authenticate__baseauth", +) +def get_api_key( + request: Request, authentication_service: AuthenticationService = depends(AuthenticationService) +) -> APIKeyResponse: + authorization = request.headers.get("Authorization") + auth = {"HTTP_AUTHORIZATION": authorization} + return authentication_service.get_api_key(auth, request) diff --git a/lib/tool_shed/webapp/api2/categories.py b/lib/tool_shed/webapp/api2/categories.py index e24031641c0a..311e1a99f448 100644 --- a/lib/tool_shed/webapp/api2/categories.py +++ b/lib/tool_shed/webapp/api2/categories.py @@ -27,70 +27,72 @@ router = Router(tags=["categories"]) -class FastAPICategories: - @router.post( - "/api/categories", - description="create a category", - operation_id="categories__create", - require_admin=True, - ) - def create( - trans: SessionRequestContext = DependsOnTrans, - request: CreateCategoryRequest = Body(...), - category_manager: CategoryManager = depends(CategoryManager), - ) -> CategoryResponse: - category = category_manager.create(trans, request) - return category_manager.to_model(category) +@router.post( + "/api/categories", + description="create a category", + operation_id="categories__create", + require_admin=True, +) +def create( + trans: SessionRequestContext = DependsOnTrans, + request: CreateCategoryRequest = Body(...), + category_manager: CategoryManager = depends(CategoryManager), +) -> CategoryResponse: + category = category_manager.create(trans, request) + return category_manager.to_model(category) - @router.get( - "/api/categories", - description="index category", - operation_id="categories__index", - ) - def index( - trans: SessionRequestContext = DependsOnTrans, - category_manager: CategoryManager = depends(CategoryManager), - ) -> List[CategoryResponse]: - """ - Return a list of dictionaries that contain information about each Category. - """ - deleted = False - categories = category_manager.index_db(trans, deleted) - return [category_manager.to_model(c) for c in categories] - @router.get( - "/api/categories/{encoded_category_id}", - description="show category", - operation_id="categories__show", - ) - def show( - encoded_category_id: str = CategoryIdPathParam, - category_manager: CategoryManager = depends(CategoryManager), - ) -> CategoryResponse: - """ - Return a list of dictionaries that contain information about each Category. - """ - category = category_manager.get(encoded_category_id) - return category_manager.to_model(category) +@router.get( + "/api/categories", + description="index category", + operation_id="categories__index", +) +def index( + trans: SessionRequestContext = DependsOnTrans, + category_manager: CategoryManager = depends(CategoryManager), +) -> List[CategoryResponse]: + """ + Return a list of dictionaries that contain information about each Category. + """ + deleted = False + categories = category_manager.index_db(trans, deleted) + return [category_manager.to_model(c) for c in categories] - @router.get( - "/api/categories/{encoded_category_id}/repositories", - description="display repositories by category", - operation_id="categories__repositories", + +@router.get( + "/api/categories/{encoded_category_id}", + description="show category", + operation_id="categories__show", +) +def show( + encoded_category_id: str = CategoryIdPathParam, + category_manager: CategoryManager = depends(CategoryManager), +) -> CategoryResponse: + """ + Return a list of dictionaries that contain information about each Category. + """ + category = category_manager.get(encoded_category_id) + return category_manager.to_model(category) + + +@router.get( + "/api/categories/{encoded_category_id}/repositories", + description="display repositories by category", + operation_id="categories__repositories", +) +def repositories( + trans: SessionRequestContext = DependsOnTrans, + encoded_category_id: str = CategoryIdPathParam, + installable: bool = CategoryRepositoriesInstallableQueryParam, + sort_key: str = CategoryRepositoriesSortKeyQueryParam, + sort_order: str = CategoryRepositoriesSortOrderQueryParam, + page: Optional[int] = CategoryRepositoriesPageQueryParam, +) -> RepositoriesByCategory: + return repositories_by_category( + trans.app, + encoded_category_id, + page=page, + sort_key=sort_key, + sort_order=sort_order, + installable=installable, ) - def repositories( - trans: SessionRequestContext = DependsOnTrans, - encoded_category_id: str = CategoryIdPathParam, - installable: bool = CategoryRepositoriesInstallableQueryParam, - sort_key: str = CategoryRepositoriesSortKeyQueryParam, - sort_order: str = CategoryRepositoriesSortOrderQueryParam, - page: Optional[int] = CategoryRepositoriesPageQueryParam, - ) -> RepositoriesByCategory: - return repositories_by_category( - trans.app, - encoded_category_id, - page=page, - sort_key=sort_key, - sort_order=sort_order, - installable=installable, - ) diff --git a/lib/tool_shed/webapp/api2/configuration.py b/lib/tool_shed/webapp/api2/configuration.py index 6228ba8af1ad..f8a6447e30af 100644 --- a/lib/tool_shed/webapp/api2/configuration.py +++ b/lib/tool_shed/webapp/api2/configuration.py @@ -8,14 +8,13 @@ router = Router(tags=["configuration"]) -class FastAPIConfiguration: - @router.get( - "/api/version", - operation_id="configuration__version", +@router.get( + "/api/version", + operation_id="configuration__version", +) +def version(app: ToolShedApp = depends(ToolShedApp)) -> Version: + return Version( + version_major=app.config.version_major, + version=app.config.version, + api_version="v2", ) - def version(app: ToolShedApp = depends(ToolShedApp)) -> Version: - return Version( - version_major=app.config.version_major, - version=app.config.version, - api_version="v2", - ) diff --git a/lib/tool_shed/webapp/api2/repositories.py b/lib/tool_shed/webapp/api2/repositories.py index bdab71704917..e4d870d957ab 100644 --- a/lib/tool_shed/webapp/api2/repositories.py +++ b/lib/tool_shed/webapp/api2/repositories.py @@ -29,12 +29,12 @@ check_updates, create_repository, get_install_info, - get_ordered_installable_revisions, + get_ordered_installable_revisions as do_get_ordered_installable_revisions, get_repository_metadata_dict, get_repository_metadata_for_management, index_repositories, readmes, - reset_metadata_on_repository, + reset_metadata_on_repository as do_reset_metadata_on_repository, search, to_detailed_model, to_model, @@ -95,408 +95,425 @@ class RepositoryUpdateRequestFormData(RepositoryUpdateRequest): pass -class FastAPIRepositories: - @router.get( - "/api/repositories", - description="Get a list of repositories or perform a search.", - operation_id="repositories__index", - ) - def index( - q: Optional[str] = RepositoryIndexQueryParam, - page: Optional[int] = RepositorySearchPageQueryParam, - page_size: Optional[int] = RepositorySearchPageSizeQueryParam, - deleted: Optional[bool] = RepositoryIndexDeletedQueryParam, - owner: Optional[str] = RepositoryIndexOwnerQueryParam, - name: Optional[str] = RepositoryIndexNameQueryParam, - trans: SessionRequestContext = DependsOnTrans, - app: ToolShedApp = depends(ToolShedApp), - ) -> IndexResponse: - if q: - assert page is not None - assert page_size is not None - search_results = search(trans, q, page, page_size) - return RepositorySearchResults(**search_results) - # See API notes - was added in https://github.com/galaxyproject/galaxy/pull/3626/files - # but I think is currently unused. So probably we should just drop it until someone - # complains. - # elif params.tool_ids: - # response = index_tool_ids(app, params.tool_ids) - # return response - else: - repositories = index_repositories(app, name, owner, deleted or False) - return [to_model(app, r) for r in repositories] - - @router.get( - "/api/repositories/get_repository_revision_install_info", - description="Get information used by the install client to install this repository.", - operation_id="repositories__legacy_install_info", +@router.get( + "/api/repositories", + description="Get a list of repositories or perform a search.", + operation_id="repositories__index", +) +def index( + q: Optional[str] = RepositoryIndexQueryParam, + page: Optional[int] = RepositorySearchPageQueryParam, + page_size: Optional[int] = RepositorySearchPageSizeQueryParam, + deleted: Optional[bool] = RepositoryIndexDeletedQueryParam, + owner: Optional[str] = RepositoryIndexOwnerQueryParam, + name: Optional[str] = RepositoryIndexNameQueryParam, + trans: SessionRequestContext = DependsOnTrans, + app: ToolShedApp = depends(ToolShedApp), +) -> IndexResponse: + if q: + assert page is not None + assert page_size is not None + search_results = search(trans, q, page, page_size) + return RepositorySearchResults(**search_results) + # See API notes - was added in https://github.com/galaxyproject/galaxy/pull/3626/files + # but I think is currently unused. So probably we should just drop it until someone + # complains. + # elif params.tool_ids: + # response = index_tool_ids(app, params.tool_ids) + # return response + else: + repositories = index_repositories(app, name, owner, deleted or False) + return [to_model(app, r) for r in repositories] + + +@router.get( + "/api/repositories/get_repository_revision_install_info", + description="Get information used by the install client to install this repository.", + operation_id="repositories__legacy_install_info", +) +def legacy_install_info( + trans: SessionRequestContext = DependsOnTrans, + name: str = RequiredRepoNameParam, + owner: str = RequiredRepoOwnerParam, + changeset_revision: str = RequiredChangesetParam, +) -> list: + legacy_install_info = get_install_info( + trans, + name, + owner, + changeset_revision, ) - def legacy_install_info( - trans: SessionRequestContext = DependsOnTrans, - name: str = RequiredRepoNameParam, - owner: str = RequiredRepoOwnerParam, - changeset_revision: str = RequiredChangesetParam, - ) -> list: - legacy_install_info = get_install_info( - trans, - name, - owner, - changeset_revision, - ) - return list(legacy_install_info) + return list(legacy_install_info) - @router.get( - "/api/repositories/install_info", - description="Get information used by the install client to install this repository.", - operation_id="repositories__install_info", - ) - def install_info( - trans: SessionRequestContext = DependsOnTrans, - name: str = RequiredRepoNameParam, - owner: str = RequiredRepoOwnerParam, - changeset_revision: str = RequiredChangesetParam, - ) -> InstallInfo: - # A less problematic version of the above API, but I guess we - # need to maintain the older version for older Galaxy API clients - # for... sometime... or forever. - legacy_install_info = get_install_info( - trans, - name, - owner, - changeset_revision, - ) - return from_legacy_install_info(legacy_install_info) - - @router.get( - "/api/repositories/{encoded_repository_id}/metadata", - description="Get information about repository metadata", - operation_id="repositories__metadata", - # See comment below. - # response_model=RepositoryMetadata, - ) - def metadata( - encoded_repository_id: str = RepositoryIdPathParam, - downloadable_only: bool = DownloadableQueryParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> dict: - recursive = True - as_dict = get_repository_metadata_dict(app, encoded_repository_id, recursive, downloadable_only) - # fails 1020 if we try to use the model - I guess repository dependencies - # are getting lost - return as_dict - # return _hack_fastapi_4428(as_dict) - - @router.get( - "/api_internal/repositories/{encoded_repository_id}/metadata", - description="Get information about repository metadata", - operation_id="repositories__internal_metadata", - response_model=RepositoryMetadata, - ) - def metadata_internal( - encoded_repository_id: str = RepositoryIdPathParam, - downloadable_only: bool = DownloadableQueryParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> dict: - recursive = True - as_dict = get_repository_metadata_dict(app, encoded_repository_id, recursive, downloadable_only) - return _hack_fastapi_4428(as_dict) - - @router.get( - "/api/repositories/get_ordered_installable_revisions", - description="Get an ordered list of the repository changeset revisions that are installable", - operation_id="repositories__get_ordered_installable_revisions", - ) - def get_ordered_installable_revisions( - owner: Optional[str] = OptionalRepositoryOwnerParam, - name: Optional[str] = OptionalRepositoryNameParam, - tsr_id: Optional[str] = OptionalRepositoryIdParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> List[str]: - return get_ordered_installable_revisions(app, name, owner, tsr_id) - - @router.post( - "/api/repositories/reset_metadata_on_repository", - description="reset metadata on a repository", - operation_id="repositories__reset_legacy", - ) - def reset_metadata_on_repository_legacy( - trans: SessionRequestContext = DependsOnTrans, - request: ResetMetadataOnRepositoryRequest = depend_on_either_json_or_form_data( - ResetMetadataOnRepositoryRequest - ), - ) -> ResetMetadataOnRepositoryResponse: - return reset_metadata_on_repository(trans, request.repository_id) - - @router.post( - "/api/repositories/{encoded_repository_id}/reset_metadata", - description="reset metadata on a repository", - operation_id="repositories__reset", - ) - def reset_metadata_on_repository( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - ) -> ResetMetadataOnRepositoryResponse: - return reset_metadata_on_repository(trans, encoded_repository_id) - - @router.get( - "/api/repositories/updates", - operation_id="repositories__update", - ) - @router.get( - "/api/repositories/updates/", - ) - def updates( - owner: Optional[str] = OptionalRepositoryOwnerParam, - name: Optional[str] = OptionalRepositoryNameParam, - changeset_revision: str = RequiredRepositoryChangesetRevisionParam, - hexlify: Optional[bool] = OptionalHexlifyParam, - app: ToolShedApp = depends(ToolShedApp), - ): - request = UpdatesRequest( - name=name, - owner=owner, - changeset_revision=changeset_revision, - hexlify=hexlify, - ) - return Response(content=check_updates(app, request)) - @router.post( - "/api/repositories", - description="create a new repository", - operation_id="repositories__create", +@router.get( + "/api/repositories/install_info", + description="Get information used by the install client to install this repository.", + operation_id="repositories__install_info", +) +def install_info( + trans: SessionRequestContext = DependsOnTrans, + name: str = RequiredRepoNameParam, + owner: str = RequiredRepoOwnerParam, + changeset_revision: str = RequiredChangesetParam, +) -> InstallInfo: + # A less problematic version of the above API, but I guess we + # need to maintain the older version for older Galaxy API clients + # for... sometime... or forever. + legacy_install_info = get_install_info( + trans, + name, + owner, + changeset_revision, ) - def create( - trans: SessionRequestContext = DependsOnTrans, - request: CreateRepositoryRequest = Body(...), - app: ToolShedApp = depends(ToolShedApp), - ) -> Repository: - db_repository = create_repository( - trans, - request, - ) - return to_model(app, db_repository) + return from_legacy_install_info(legacy_install_info) - @router.get( - "/api/repositories/{encoded_repository_id}", - operation_id="repositories__show", - ) - def show( - encoded_repository_id: str = RepositoryIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> DetailedRepository: - repository = get_repository_in_tool_shed(app, encoded_repository_id) - return to_detailed_model(app, repository) - @router.get( - "/api/repositories/{encoded_repository_id}/permissions", - operation_id="repositories__permissions", - ) - def permissions( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> RepositoryPermissions: - repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_update_repo(trans, repository): - raise InsufficientPermissionsException( - "You do not have permission to inspect repository repository permissions." - ) - return RepositoryPermissions( - allow_push=trans.app.security_agent.usernames_that_can_push(repository), - can_manage=can_manage_repo(trans, repository), - can_push=can_update_repo(trans, repository), - ) +@router.get( + "/api/repositories/{encoded_repository_id}/metadata", + description="Get information about repository metadata", + operation_id="repositories__metadata", + # See comment below. + # response_model=RepositoryMetadata, +) +def metadata( + encoded_repository_id: str = RepositoryIdPathParam, + downloadable_only: bool = DownloadableQueryParam, + app: ToolShedApp = depends(ToolShedApp), +) -> dict: + recursive = True + as_dict = get_repository_metadata_dict(app, encoded_repository_id, recursive, downloadable_only) + # fails 1020 if we try to use the model - I guess repository dependencies + # are getting lost + return as_dict + # return _hack_fastapi_4428(as_dict) + + +@router.get( + "/api_internal/repositories/{encoded_repository_id}/metadata", + description="Get information about repository metadata", + operation_id="repositories__internal_metadata", + response_model=RepositoryMetadata, +) +def metadata_internal( + encoded_repository_id: str = RepositoryIdPathParam, + downloadable_only: bool = DownloadableQueryParam, + app: ToolShedApp = depends(ToolShedApp), +) -> dict: + recursive = True + as_dict = get_repository_metadata_dict(app, encoded_repository_id, recursive, downloadable_only) + return _hack_fastapi_4428(as_dict) + + +@router.get( + "/api/repositories/get_ordered_installable_revisions", + description="Get an ordered list of the repository changeset revisions that are installable", + operation_id="repositories__get_ordered_installable_revisions", +) +def get_ordered_installable_revisions( + owner: Optional[str] = OptionalRepositoryOwnerParam, + name: Optional[str] = OptionalRepositoryNameParam, + tsr_id: Optional[str] = OptionalRepositoryIdParam, + app: ToolShedApp = depends(ToolShedApp), +) -> List[str]: + return do_get_ordered_installable_revisions(app, name, owner, tsr_id) + + +@router.post( + "/api/repositories/reset_metadata_on_repository", + description="reset metadata on a repository", + operation_id="repositories__reset_legacy", +) +def reset_metadata_on_repository_legacy( + trans: SessionRequestContext = DependsOnTrans, + request: ResetMetadataOnRepositoryRequest = depend_on_either_json_or_form_data(ResetMetadataOnRepositoryRequest), +) -> ResetMetadataOnRepositoryResponse: + return do_reset_metadata_on_repository(trans, request.repository_id) - @router.get( - "/api/repositories/{encoded_repository_id}/allow_push", - operation_id="repositories__show_allow_push", - ) - def show_allow_push( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> List[str]: - repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_manage_repo(trans, repository): - raise InsufficientPermissionsException("You do not have permission to update this repository.") - return trans.app.security_agent.usernames_that_can_push(repository) - @router.post( - "/api/repositories/{encoded_repository_id}/allow_push/{username}", - operation_id="repositories__add_allow_push", - ) - def add_allow_push( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - username: str = UsernameIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> List[str]: - repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_manage_repo(trans, repository): - raise InsufficientPermissionsException("You do not have permission to update this repository.") - repository.set_allow_push([username]) - return trans.app.security_agent.usernames_that_can_push(repository) +@router.post( + "/api/repositories/{encoded_repository_id}/reset_metadata", + description="reset metadata on a repository", + operation_id="repositories__reset", +) +def reset_metadata_on_repository( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, +) -> ResetMetadataOnRepositoryResponse: + return do_reset_metadata_on_repository(trans, encoded_repository_id) - @router.put( - "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", - operation_id="repositories__set_malicious", - status_code=status.HTTP_204_NO_CONTENT, - ) - def set_malicious( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - changeset_revision: str = ChangesetRevisionPathParam, - ): - repository_metadata = get_repository_metadata_for_management(trans, encoded_repository_id, changeset_revision) - repository_metadata.malicious = True - trans.sa_session.add(repository_metadata) - with transaction(trans.sa_session): - trans.sa_session.commit() - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.delete( - "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", - operation_id="repositories__unset_malicious", - status_code=status.HTTP_204_NO_CONTENT, - ) - def unset_malicious( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - changeset_revision: str = ChangesetRevisionPathParam, - ): - repository_metadata = get_repository_metadata_for_management(trans, encoded_repository_id, changeset_revision) - repository_metadata.malicious = False - trans.sa_session.add(repository_metadata) - with transaction(trans.sa_session): - trans.sa_session.commit() - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.put( - "/api/repositories/{encoded_repository_id}/deprecated", - operation_id="repositories__set_deprecated", - status_code=status.HTTP_204_NO_CONTENT, + +@router.get( + "/api/repositories/updates", + operation_id="repositories__update", +) +@router.get( + "/api/repositories/updates/", +) +def updates( + owner: Optional[str] = OptionalRepositoryOwnerParam, + name: Optional[str] = OptionalRepositoryNameParam, + changeset_revision: str = RequiredRepositoryChangesetRevisionParam, + hexlify: Optional[bool] = OptionalHexlifyParam, + app: ToolShedApp = depends(ToolShedApp), +): + request = UpdatesRequest( + name=name, + owner=owner, + changeset_revision=changeset_revision, + hexlify=hexlify, ) - def set_deprecated( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ): - repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_manage_repo(trans, repository): - raise InsufficientPermissionsException("You do not have permission to update this repository.") - repository.deprecated = True - trans.sa_session.add(repository) - with transaction(trans.sa_session): - trans.sa_session.commit() - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.delete( - "/api/repositories/{encoded_repository_id}/deprecated", - operation_id="repositories__unset_deprecated", - status_code=status.HTTP_204_NO_CONTENT, + return Response(content=check_updates(app, request)) + + +@router.post( + "/api/repositories", + description="create a new repository", + operation_id="repositories__create", +) +def create( + trans: SessionRequestContext = DependsOnTrans, + request: CreateRepositoryRequest = Body(...), + app: ToolShedApp = depends(ToolShedApp), +) -> Repository: + db_repository = create_repository( + trans, + request, ) - def unset_deprecated( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ): - repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_manage_repo(trans, repository): - raise InsufficientPermissionsException("You do not have permission to update this repository.") - repository.deprecated = False - trans.sa_session.add(repository) - with transaction(trans.sa_session): - trans.sa_session.commit() - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.delete( - "/api/repositories/{encoded_repository_id}/allow_push/{username}", - operation_id="repositories__remove_allow_push", + return to_model(app, db_repository) + + +@router.get( + "/api/repositories/{encoded_repository_id}", + operation_id="repositories__show", +) +def show( + encoded_repository_id: str = RepositoryIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> DetailedRepository: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + return to_detailed_model(app, repository) + + +@router.get( + "/api/repositories/{encoded_repository_id}/permissions", + operation_id="repositories__permissions", +) +def permissions( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> RepositoryPermissions: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_update_repo(trans, repository): + raise InsufficientPermissionsException( + "You do not have permission to inspect repository repository permissions." + ) + return RepositoryPermissions( + allow_push=trans.app.security_agent.usernames_that_can_push(repository), + can_manage=can_manage_repo(trans, repository), + can_push=can_update_repo(trans, repository), ) - def remove_allow_push( - trans: SessionRequestContext = DependsOnTrans, - encoded_repository_id: str = RepositoryIdPathParam, - username: str = UsernameIdPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> List[str]: + + +@router.get( + "/api/repositories/{encoded_repository_id}/allow_push", + operation_id="repositories__show_allow_push", +) +def show_allow_push( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> List[str]: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_manage_repo(trans, repository): + raise InsufficientPermissionsException("You do not have permission to update this repository.") + return trans.app.security_agent.usernames_that_can_push(repository) + + +@router.post( + "/api/repositories/{encoded_repository_id}/allow_push/{username}", + operation_id="repositories__add_allow_push", +) +def add_allow_push( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + username: str = UsernameIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> List[str]: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_manage_repo(trans, repository): + raise InsufficientPermissionsException("You do not have permission to update this repository.") + repository.set_allow_push([username]) + return trans.app.security_agent.usernames_that_can_push(repository) + + +@router.put( + "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", + operation_id="repositories__set_malicious", + status_code=status.HTTP_204_NO_CONTENT, +) +def set_malicious( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + changeset_revision: str = ChangesetRevisionPathParam, +): + repository_metadata = get_repository_metadata_for_management(trans, encoded_repository_id, changeset_revision) + repository_metadata.malicious = True + trans.sa_session.add(repository_metadata) + with transaction(trans.sa_session): + trans.sa_session.commit() + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.delete( + "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/malicious", + operation_id="repositories__unset_malicious", + status_code=status.HTTP_204_NO_CONTENT, +) +def unset_malicious( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + changeset_revision: str = ChangesetRevisionPathParam, +): + repository_metadata = get_repository_metadata_for_management(trans, encoded_repository_id, changeset_revision) + repository_metadata.malicious = False + trans.sa_session.add(repository_metadata) + with transaction(trans.sa_session): + trans.sa_session.commit() + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.put( + "/api/repositories/{encoded_repository_id}/deprecated", + operation_id="repositories__set_deprecated", + status_code=status.HTTP_204_NO_CONTENT, +) +def set_deprecated( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +): + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_manage_repo(trans, repository): + raise InsufficientPermissionsException("You do not have permission to update this repository.") + repository.deprecated = True + trans.sa_session.add(repository) + with transaction(trans.sa_session): + trans.sa_session.commit() + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.delete( + "/api/repositories/{encoded_repository_id}/deprecated", + operation_id="repositories__unset_deprecated", + status_code=status.HTTP_204_NO_CONTENT, +) +def unset_deprecated( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +): + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_manage_repo(trans, repository): + raise InsufficientPermissionsException("You do not have permission to update this repository.") + repository.deprecated = False + trans.sa_session.add(repository) + with transaction(trans.sa_session): + trans.sa_session.commit() + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.delete( + "/api/repositories/{encoded_repository_id}/allow_push/{username}", + operation_id="repositories__remove_allow_push", +) +def remove_allow_push( + trans: SessionRequestContext = DependsOnTrans, + encoded_repository_id: str = RepositoryIdPathParam, + username: str = UsernameIdPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> List[str]: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + if not can_manage_repo(trans, repository): + raise InsufficientPermissionsException("You do not have permission to update this repository.") + repository.set_allow_push(None, remove_auth=username) + return trans.app.security_agent.usernames_that_can_push(repository) + + +@router.post( + "/api/repositories/{encoded_repository_id}/changeset_revision", + description="upload new revision to the repository", + operation_id="repositories__create_revision", +) +async def create_changeset_revision( + request: Request, + encoded_repository_id: str = RepositoryIdPathParam, + commit_message: Optional[str] = CommitMessageQueryParam, + trans: SessionRequestContext = DependsOnTrans, + files: Optional[List[UploadFile]] = None, + revision_request: RepositoryUpdateRequest = Depends(RepositoryUpdateRequestFormData.as_form), # type: ignore[attr-defined] + app: ToolShedApp = depends(ToolShedApp), +) -> RepositoryUpdate: + try: + # Code stolen from Marius' work in Galaxy's Tools API. + + files2: List[StarletteUploadFile] = cast(List[StarletteUploadFile], files or []) + # FastAPI's UploadFile is a very light wrapper around starlette's UploadFile + if not files2: + data = await request.form() + for value in data.values(): + if isinstance(value, StarletteUploadFile): + files2.append(value) + repository = get_repository_in_tool_shed(app, encoded_repository_id) - if not can_manage_repo(trans, repository): + + if not can_update_repo(trans, repository): raise InsufficientPermissionsException("You do not have permission to update this repository.") - repository.set_allow_push(None, remove_auth=username) - return trans.app.security_agent.usernames_that_can_push(repository) - @router.post( - "/api/repositories/{encoded_repository_id}/changeset_revision", - description="upload new revision to the repository", - operation_id="repositories__create_revision", - ) - async def create_changeset_revision( - request: Request, - encoded_repository_id: str = RepositoryIdPathParam, - commit_message: Optional[str] = CommitMessageQueryParam, - trans: SessionRequestContext = DependsOnTrans, - files: Optional[List[UploadFile]] = None, - revision_request: RepositoryUpdateRequest = Depends(RepositoryUpdateRequestFormData.as_form), # type: ignore[attr-defined] - app: ToolShedApp = depends(ToolShedApp), - ) -> RepositoryUpdate: + assert trans.user + assert files2 + the_file = files2[0] + with tempfile.NamedTemporaryFile( + dir=trans.app.config.new_file_path, prefix="upload_file_data_", delete=False + ) as dest: + upload_file_like: IO[bytes] = the_file.file + shutil.copyfileobj(upload_file_like, dest) # type: ignore[misc] # https://github.com/python/mypy/issues/15031 + the_file.file.close() + filename = dest.name try: - # Code stolen from Marius' work in Galaxy's Tools API. - - files2: List[StarletteUploadFile] = cast(List[StarletteUploadFile], files or []) - # FastAPI's UploadFile is a very light wrapper around starlette's UploadFile - if not files2: - data = await request.form() - for value in data.values(): - if isinstance(value, StarletteUploadFile): - files2.append(value) - - repository = get_repository_in_tool_shed(app, encoded_repository_id) - - if not can_update_repo(trans, repository): - raise InsufficientPermissionsException("You do not have permission to update this repository.") - - assert trans.user - assert files2 - the_file = files2[0] - with tempfile.NamedTemporaryFile( - dir=trans.app.config.new_file_path, prefix="upload_file_data_", delete=False - ) as dest: - upload_file_like: IO[bytes] = the_file.file - shutil.copyfileobj(upload_file_like, dest) # type: ignore[misc] # https://github.com/python/mypy/issues/15031 - the_file.file.close() - filename = dest.name - try: - message = upload_tar_and_set_metadata( - trans, - trans.request.host, - repository, - filename, - commit_message or revision_request.commit_message or "Uploaded", - ) - return RepositoryUpdate(__root__=ValidRepostiroyUpdateMessage(message=message)) - finally: - if os.path.exists(filename): - os.remove(filename) - except Exception: - import logging - - log = logging.getLogger(__name__) - log.exception("Problem in here...") - raise - - @router.get( - "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/readmes", - description="fetch readmes for repository revision", - operation_id="repositories__readmes", - response_model=RepositoryRevisionReadmes, - ) - def get_readmes( - encoded_repository_id: str = RepositoryIdPathParam, - changeset_revision: str = ChangesetRevisionPathParam, - app: ToolShedApp = depends(ToolShedApp), - ) -> dict: - repository = get_repository_in_tool_shed(app, encoded_repository_id) - return readmes(app, repository, changeset_revision) + message = upload_tar_and_set_metadata( + trans, + trans.request.host, + repository, + filename, + commit_message or revision_request.commit_message or "Uploaded", + ) + return RepositoryUpdate(__root__=ValidRepostiroyUpdateMessage(message=message)) + finally: + if os.path.exists(filename): + os.remove(filename) + except Exception: + import logging + + log = logging.getLogger(__name__) + log.exception("Problem in here...") + raise + + +@router.get( + "/api/repositories/{encoded_repository_id}/revisions/{changeset_revision}/readmes", + description="fetch readmes for repository revision", + operation_id="repositories__readmes", + response_model=RepositoryRevisionReadmes, +) +def get_readmes( + encoded_repository_id: str = RepositoryIdPathParam, + changeset_revision: str = ChangesetRevisionPathParam, + app: ToolShedApp = depends(ToolShedApp), +) -> dict: + repository = get_repository_in_tool_shed(app, encoded_repository_id) + return readmes(app, repository, changeset_revision) def _hack_fastapi_4428(as_dict) -> dict: diff --git a/lib/tool_shed/webapp/api2/tools.py b/lib/tool_shed/webapp/api2/tools.py index 67ae1ed120ca..8f23d760fe5d 100644 --- a/lib/tool_shed/webapp/api2/tools.py +++ b/lib/tool_shed/webapp/api2/tools.py @@ -10,8 +10,8 @@ from tool_shed.managers.tools import search from tool_shed.managers.trs import ( get_tool, - service_info, - tool_classes, + service_info as get_service_info, + tool_classes as get_tool_classes, ) from tool_shed.structured_app import ToolShedApp from tool_shed.util.shed_index import build_index @@ -42,74 +42,79 @@ ) -class FastAPITools: - @router.get( - "/api/tools", - operation_id="tools__index", - ) - def index( - q: str = ToolsIndexQueryParam, - page: int = RepositorySearchPageQueryParam, - page_size: int = RepositorySearchPageSizeQueryParam, - trans: SessionRequestContext = DependsOnTrans, - ): - search_results = search(trans, q, page, page_size) - return search_results - - @router.put( - "/api/tools/build_search_index", - operation_id="tools__build_search_index", - require_admin=True, - ) - def build_search_index(app: ToolShedApp = depends(ToolShedApp)) -> BuildSearchIndexResponse: - """Not part of the stable API, just something to simplify - bootstrapping tool sheds, scripting, testing, etc... - """ - config = app.config - repos_indexed, tools_indexed = build_index( - config.whoosh_index_dir, - config.file_path, - config.hgweb_config_dir, - config.database_connection, - ) - return 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(request: Request, app: ToolShedApp = depends(ToolShedApp)) -> Service: - return service_info(app, request.url) - - @router.get("/api/ga4gh/trs/v2/toolClasses", operation_id="tools__trs_tool_classes") - def tool_classes() -> List[ToolClass]: - return tool_classes() - - @router.get( - "/api/ga4gh/trs/v2/tools", - operation_id="tools__trs_index", - ) - def trs_index(): - # 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", +@router.get( + "/api/tools", + operation_id="tools__index", +) +def index( + q: str = ToolsIndexQueryParam, + page: int = RepositorySearchPageQueryParam, + page_size: int = RepositorySearchPageSizeQueryParam, + trans: SessionRequestContext = DependsOnTrans, +): + search_results = search(trans, q, page, page_size) + return search_results + + +@router.put( + "/api/tools/build_search_index", + operation_id="tools__build_search_index", + require_admin=True, +) +def build_search_index(app: ToolShedApp = depends(ToolShedApp)) -> BuildSearchIndexResponse: + """Not part of the stable API, just something to simplify + bootstrapping tool sheds, scripting, testing, etc... + """ + config = app.config + repos_indexed, tools_indexed = build_index( + config.whoosh_index_dir, + config.file_path, + config.hgweb_config_dir, + config.database_connection, ) - def trs_get( - 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", + return BuildSearchIndexResponse( + repositories_indexed=repos_indexed, + tools_indexed=tools_indexed, ) - def trs_get_versions( - trans: SessionRequestContext = DependsOnTrans, - tool_id: str = TOOL_ID_PATH_PARAM, - ) -> List[ToolVersion]: - return get_tool(trans, tool_id).versions + + +@router.get("/api/ga4gh/trs/v2/service-info", operation_id="tools_trs_service_info") +def service_info(request: Request, app: ToolShedApp = depends(ToolShedApp)) -> Service: + return get_service_info(app, request.url) + + +@router.get("/api/ga4gh/trs/v2/toolClasses", operation_id="tools__trs_tool_classes") +def tool_classes() -> List[ToolClass]: + return get_tool_classes() + + +@router.get( + "/api/ga4gh/trs/v2/tools", + operation_id="tools__trs_index", +) +def trs_index(): + # 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( + 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( + trans: SessionRequestContext = DependsOnTrans, + tool_id: str = TOOL_ID_PATH_PARAM, +) -> List[ToolVersion]: + return get_tool(trans, tool_id).versions diff --git a/lib/tool_shed/webapp/api2/users.py b/lib/tool_shed/webapp/api2/users.py index b114561b1dc9..8a2362425798 100644 --- a/lib/tool_shed/webapp/api2/users.py +++ b/lib/tool_shed/webapp/api2/users.py @@ -30,7 +30,7 @@ from tool_shed.managers.users import ( api_create_user, get_api_user, - index, + index as get_users, ) from tool_shed.structured_app import ToolShedApp from tool_shed.webapp.model import ( @@ -98,189 +98,198 @@ class UiChangePasswordRequest(BaseModel): INVALID_LOGIN_OR_PASSWORD = "Invalid login or password" -class FastAPIUsers: - @router.get( - "/api/users", - description="index users", - operation_id="users__index", - ) - def index(trans: SessionRequestContext = DependsOnTrans) -> List[User]: - deleted = False - return index(trans.app, deleted) - - @router.post( - "/api/users", - description="create a user", - operation_id="users__create", - require_admin=True, - ) - def create(trans: SessionRequestContext = DependsOnTrans, request: CreateUserRequest = Body(...)) -> User: - return api_create_user(trans, request) +@router.get( + "/api/users", + description="index users", + operation_id="users__index", +) +def index(trans: SessionRequestContext = DependsOnTrans) -> List[User]: + deleted = False + return get_users(trans.app, deleted) - @router.get( - "/api/users/current", - description="show current user", - operation_id="users__current", - ) - def current(trans: SessionRequestContext = DependsOnTrans) -> User: - user = trans.user - if not user: - raise ObjectNotFound() - return get_api_user(trans.app, user) +@router.post( + "/api/users", + description="create a user", + operation_id="users__create", + require_admin=True, +) +def create(trans: SessionRequestContext = DependsOnTrans, request: CreateUserRequest = Body(...)) -> User: + return api_create_user(trans, request) - @router.get( - "/api/users/{encoded_user_id}", - description="show a user", - operation_id="users__show", - ) - def show(trans: SessionRequestContext = DependsOnTrans, encoded_user_id: str = UserIdPathParam) -> User: - user = suc.get_user(trans.app, encoded_user_id) - if user is None: - raise ObjectNotFound() - return get_api_user(trans.app, user) - - @router.get( - "/api/users/{encoded_user_id}/api_key", - name="get_or_create_api_key", - summary="Return the user's API key", - operation_id="users__get_or_create_api_key", - ) - def get_or_create_api_key( - trans: SessionRequestContext = DependsOnTrans, - encoded_user_id: str = UserIdPathParam, - api_key_manager: ApiKeyManager = depends(ApiKeyManager), - ) -> str: - user = _get_user(trans, encoded_user_id) - return api_key_manager.get_or_create_api_key(user) - - @router.post( - "/api/users/{encoded_user_id}/api_key", - summary="Creates a new API key for the user", - operation_id="users__create_api_key", - ) - def create_api_key( - trans: SessionRequestContext = DependsOnTrans, - encoded_user_id: str = UserIdPathParam, - api_key_manager: ApiKeyManager = depends(ApiKeyManager), - ) -> str: - user = _get_user(trans, encoded_user_id) - return api_key_manager.create_api_key(user).key - - @router.delete( - "/api/users/{encoded_user_id}/api_key", - summary="Delete the current API key of the user", - status_code=status.HTTP_204_NO_CONTENT, - operation_id="users__delete_api_key", - ) - def delete_api_key( - trans: SessionRequestContext = DependsOnTrans, - encoded_user_id: str = UserIdPathParam, - api_key_manager: ApiKeyManager = depends(ApiKeyManager), - ): - user = _get_user(trans, encoded_user_id) - api_key_manager.delete_api_key(user) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.post( - "/api_internal/register", - description="register a user", - operation_id="users__internal_register", - ) - def register( - trans: SessionRequestContext = DependsOnTrans, - request: UiRegisterRequest = Body(...), - app: ToolShedApp = depends(ToolShedApp), - user_manager: UserManager = depends(UserManager), - ) -> UiRegisterResponse: - honeypot_field = request.bear_field - if honeypot_field != "": - message = "You've been flagged as a possible bot. If you are not, please try registering again and fill the form out carefully." - raise RequestParameterInvalidException(message) - - username = request.username - if username == "repos": - raise RequestParameterInvalidException("Cannot create a user with the username 'repos'") - user_manager.create(email=request.email, username=username, password=request.password) - if app.config.user_activation_on: - is_activation_sent = user_manager.send_activation_email(trans, request.email, username) - if is_activation_sent: - return UiRegisterResponse(email=request.email, activation_sent=True) - else: - return UiRegisterResponse( - email=request.email, - activation_sent=False, - activation_error=True, - contact_email=app.config.error_email_to, - ) + +@router.get( + "/api/users/current", + description="show current user", + operation_id="users__current", +) +def current(trans: SessionRequestContext = DependsOnTrans) -> User: + user = trans.user + if not user: + raise ObjectNotFound() + + return get_api_user(trans.app, user) + + +@router.get( + "/api/users/{encoded_user_id}", + description="show a user", + operation_id="users__show", +) +def show(trans: SessionRequestContext = DependsOnTrans, encoded_user_id: str = UserIdPathParam) -> User: + user = suc.get_user(trans.app, encoded_user_id) + if user is None: + raise ObjectNotFound() + return get_api_user(trans.app, user) + + +@router.get( + "/api/users/{encoded_user_id}/api_key", + name="get_or_create_api_key", + summary="Return the user's API key", + operation_id="users__get_or_create_api_key", +) +def get_or_create_api_key( + trans: SessionRequestContext = DependsOnTrans, + encoded_user_id: str = UserIdPathParam, + api_key_manager: ApiKeyManager = depends(ApiKeyManager), +) -> str: + user = _get_user(trans, encoded_user_id) + return api_key_manager.get_or_create_api_key(user) + + +@router.post( + "/api/users/{encoded_user_id}/api_key", + summary="Creates a new API key for the user", + operation_id="users__create_api_key", +) +def create_api_key( + trans: SessionRequestContext = DependsOnTrans, + encoded_user_id: str = UserIdPathParam, + api_key_manager: ApiKeyManager = depends(ApiKeyManager), +) -> str: + user = _get_user(trans, encoded_user_id) + return api_key_manager.create_api_key(user).key + + +@router.delete( + "/api/users/{encoded_user_id}/api_key", + summary="Delete the current API key of the user", + status_code=status.HTTP_204_NO_CONTENT, + operation_id="users__delete_api_key", +) +def delete_api_key( + trans: SessionRequestContext = DependsOnTrans, + encoded_user_id: str = UserIdPathParam, + api_key_manager: ApiKeyManager = depends(ApiKeyManager), +): + user = _get_user(trans, encoded_user_id) + api_key_manager.delete_api_key(user) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.post( + "/api_internal/register", + description="register a user", + operation_id="users__internal_register", +) +def register( + trans: SessionRequestContext = DependsOnTrans, + request: UiRegisterRequest = Body(...), + app: ToolShedApp = depends(ToolShedApp), + user_manager: UserManager = depends(UserManager), +) -> UiRegisterResponse: + honeypot_field = request.bear_field + if honeypot_field != "": + message = "You've been flagged as a possible bot. If you are not, please try registering again and fill the form out carefully." + raise RequestParameterInvalidException(message) + + username = request.username + if username == "repos": + raise RequestParameterInvalidException("Cannot create a user with the username 'repos'") + user_manager.create(email=request.email, username=username, password=request.password) + if app.config.user_activation_on: + is_activation_sent = user_manager.send_activation_email(trans, request.email, username) + if is_activation_sent: + return UiRegisterResponse(email=request.email, activation_sent=True) else: - return UiRegisterResponse(email=request.email) + return UiRegisterResponse( + email=request.email, + activation_sent=False, + activation_error=True, + contact_email=app.config.error_email_to, + ) + else: + return UiRegisterResponse(email=request.email) + - @router.put( - "/api_internal/change_password", - description="reset a user", - operation_id="users__internal_change_password", - status_code=status.HTTP_204_NO_CONTENT, +@router.put( + "/api_internal/change_password", + description="reset a user", + operation_id="users__internal_change_password", + status_code=status.HTTP_204_NO_CONTENT, +) +def change_password( + trans: SessionRequestContext = DependsOnTrans, + request: UiChangePasswordRequest = Body(...), + user_manager: UserManager = depends(UserManager), +): + password = request.password + current = request.current + if trans.user is None: + raise InsufficientPermissionsException("Must be logged into use this functionality") + user_id = trans.user.id + token = None + user, message = user_manager.change_password( + trans, password=password, current=current, token=token, confirm=password, id=user_id ) - def change_password( - trans: SessionRequestContext = DependsOnTrans, - request: UiChangePasswordRequest = Body(...), - user_manager: UserManager = depends(UserManager), - ): - password = request.password - current = request.current - if trans.user is None: - raise InsufficientPermissionsException("Must be logged into use this functionality") - user_id = trans.user.id - token = None - user, message = user_manager.change_password( - trans, password=password, current=current, token=token, confirm=password, id=user_id + if not user: + raise RequestParameterInvalidException(message) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +@router.put( + "/api_internal/login", + description="login to web UI", + operation_id="users__internal_login", +) +def internal_login( + trans: SessionRequestContext = DependsOnTrans, + request: UiLoginRequest = Body(...), + user_manager: UserManager = depends(UserManager), +) -> UiLoginResponse: + log.info(f"top of internal_login {trans.session_csrf_token}") + ensure_csrf_token(trans, request) + login = request.login + password = request.password + user = user_manager.get_user_by_identity(login) + if user is None: + raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD) + elif user.deleted: + message = ( + "This account has been marked deleted, contact your local Galaxy administrator to restore the account." ) - if not user: - raise RequestParameterInvalidException(message) - return Response(status_code=status.HTTP_204_NO_CONTENT) - - @router.put( - "/api_internal/login", - description="login to web UI", - operation_id="users__internal_login", - ) - def internal_login( - trans: SessionRequestContext = DependsOnTrans, - request: UiLoginRequest = Body(...), - user_manager: UserManager = depends(UserManager), - ) -> UiLoginResponse: - log.info(f"top of internal_login {trans.session_csrf_token}") - ensure_csrf_token(trans, request) - login = request.login - password = request.password - user = user_manager.get_user_by_identity(login) - if user is None: - raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD) - elif user.deleted: - message = ( - "This account has been marked deleted, contact your local Galaxy administrator to restore the account." - ) - if trans.app.config.error_email_to is not None: - message += f" Contact: {trans.app.config.error_email_to}." - raise InsufficientPermissionsException(message) - elif not trans.app.auth_manager.check_password(user, password, trans.request): - raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD) - else: - handle_user_login(trans, user) - return UiLoginResponse() + if trans.app.config.error_email_to is not None: + message += f" Contact: {trans.app.config.error_email_to}." + raise InsufficientPermissionsException(message) + elif not trans.app.auth_manager.check_password(user, password, trans.request): + raise InsufficientPermissionsException(INVALID_LOGIN_OR_PASSWORD) + else: + handle_user_login(trans, user) + return UiLoginResponse() - @router.put( - "/api_internal/logout", - description="logout of web UI", - operation_id="users__internal_logout", - ) - def internal_logout( - trans: SessionRequestContext = DependsOnTrans, request: UiLogoutRequest = Body(...) - ) -> UiLogoutResponse: - ensure_csrf_token(trans, request) - handle_user_logout(trans, logout_all=request.logout_all) - return UiLogoutResponse() + +@router.put( + "/api_internal/logout", + description="logout of web UI", + operation_id="users__internal_logout", +) +def internal_logout( + trans: SessionRequestContext = DependsOnTrans, request: UiLogoutRequest = Body(...) +) -> UiLogoutResponse: + ensure_csrf_token(trans, request) + handle_user_logout(trans, logout_all=request.logout_all) + return UiLogoutResponse() def _get_user(trans: SessionRequestContext, encoded_user_id: str):