From 60e89b1b2f3ca9b07348c1dbf554acec0faa7171 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 5 Jul 2024 12:30:25 -0400 Subject: [PATCH] Improved tool API. --- client/src/api/schema/schema.ts | 1029 ++++++++++++++++- .../schemas/tool_shed_config_schema.yml | 6 +- lib/galaxy/tool_util/parameters/state.py | 25 +- lib/tool_shed/managers/model_cache.py | 64 + lib/tool_shed/managers/tool_state_cache.py | 42 - lib/tool_shed/managers/tools.py | 77 +- lib/tool_shed/structured_app.py | 4 +- lib/tool_shed/webapp/api2/tools.py | 17 +- lib/tool_shed/webapp/app.py | 4 +- .../webapp/frontend/src/schema/schema.ts | 92 +- test/unit/tool_shed/_util.py | 4 +- test/unit/tool_shed/test_model_cache.py | 50 + test/unit/tool_shed/test_tool_source.py | 20 +- 13 files changed, 1307 insertions(+), 127 deletions(-) create mode 100644 lib/tool_shed/managers/model_cache.py delete mode 100644 lib/tool_shed/managers/tool_state_cache.py create mode 100644 test/unit/tool_shed/test_model_cache.py diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 2db6faeb1963..a55be9c2ed71 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -934,6 +934,10 @@ export interface paths { /** Delete tag based on history_id */ delete: operations["delete_api_histories__history_id__tags__tag_name__delete"]; }; + "/api/histories/{history_id}/tool_requests": { + /** Return all the tool requests for the tools submitted to this history. */ + get: operations["tool_requests_api_histories__history_id__tool_requests_get"]; + }; "/api/histories/{history_id}/unpublish": { /** * Removes this item from the published list. @@ -1026,6 +1030,8 @@ export interface paths { "/api/jobs": { /** Index */ get: operations["index_api_jobs_get"]; + /** Create */ + post: operations["create_api_jobs_post"]; }; "/api/jobs/search": { /** @@ -1580,6 +1586,10 @@ export interface paths { */ get: operations["reload_api_tool_data__table_name__reload_get"]; }; + "/api/tool_requests/{id}/state": { + /** Get tool request state. */ + get: operations["tool_request_state_api_tool_requests__id__state_get"]; + }; "/api/tool_shed_repositories": { /** Lists installed tool shed repositories. */ get: operations["index_api_tool_shed_repositories_get"]; @@ -1596,6 +1606,10 @@ export interface paths { /** Upload files to Galaxy */ post: operations["fetch_form_api_tools_fetch_post"]; }; + "/api/tools/{tool_id}/inputs": { + /** Get tool inputs. */ + get: operations["tool_inputs_api_tools__tool_id__inputs_get"]; + }; "/api/tours": { /** * Index @@ -2537,6 +2551,53 @@ export interface components { /** Targets */ targets: Record; }; + /** BooleanParameterModel */ + BooleanParameterModel: { + /** Argument */ + argument?: string | null; + /** Falsevalue */ + falsevalue?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_boolean + * @constant + * @enum {string} + */ + parameter_type?: "gx_boolean"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Truevalue */ + truevalue?: string | null; + /** + * Value + * @default false + */ + value?: boolean | null; + }; /** BroadcastNotificationContent */ BroadcastNotificationContent: { /** @@ -2834,6 +2895,46 @@ export interface components { * @enum {string} */ ColletionSourceType: "hda" | "ldda" | "hdca" | "new_collection"; + /** ColorParameterModel */ + ColorParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_color + * @constant + * @enum {string} + */ + parameter_type?: "gx_color"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Value */ + value?: string | null; + }; /** CompositeDataElement */ CompositeDataElement: { /** Md5 */ @@ -2976,6 +3077,81 @@ export interface components { */ source: string | null; }; + /** ConditionalParameterModel */ + ConditionalParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_conditional + * @constant + * @enum {string} + */ + parameter_type?: "gx_conditional"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Test Parameter */ + test_parameter: + | components["schemas"]["BooleanParameterModel"] + | components["schemas"]["SelectParameterModel"]; + /** Whens */ + whens: components["schemas"]["ConditionalWhen"][]; + }; + /** ConditionalWhen */ + ConditionalWhen: { + /** Discriminator */ + discriminator: boolean | string; + /** Is Default When */ + is_default_when: boolean; + /** Parameters */ + parameters: ( + | components["schemas"]["CwlIntegerParameterModel"] + | components["schemas"]["CwlFloatParameterModel"] + | components["schemas"]["CwlStringParameterModel"] + | components["schemas"]["CwlBooleanParameterModel"] + | components["schemas"]["CwlNullParameterModel"] + | components["schemas"]["CwlFileParameterModel"] + | components["schemas"]["CwlDirectoryParameterModel"] + | components["schemas"]["CwlUnionParameterModel"] + | components["schemas"]["TextParameterModel"] + | components["schemas"]["IntegerParameterModel"] + | components["schemas"]["FloatParameterModel"] + | components["schemas"]["BooleanParameterModel"] + | components["schemas"]["HiddenParameterModel"] + | components["schemas"]["SelectParameterModel"] + | components["schemas"]["DataParameterModel"] + | components["schemas"]["DataCollectionParameterModel"] + | components["schemas"]["DirectoryUriParameterModel"] + | components["schemas"]["RulesParameterModel"] + | components["schemas"]["ColorParameterModel"] + | components["schemas"]["ConditionalParameterModel"] + | components["schemas"]["RepeatParameterModel"] + )[]; + }; /** ConnectAction */ ConnectAction: { /** @@ -3709,6 +3885,165 @@ export interface components { */ username_and_slug?: string | null; }; + /** CwlBooleanParameterModel */ + CwlBooleanParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_boolean + * @constant + * @enum {string} + */ + parameter_type?: "cwl_boolean"; + }; + /** CwlDirectoryParameterModel */ + CwlDirectoryParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default cwl_directory + * @constant + * @enum {string} + */ + parameter_type?: "cwl_directory"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; + /** CwlFileParameterModel */ + CwlFileParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default cwl_file + * @constant + * @enum {string} + */ + parameter_type?: "cwl_file"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; + /** CwlFloatParameterModel */ + CwlFloatParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_float + * @constant + * @enum {string} + */ + parameter_type?: "cwl_float"; + }; + /** CwlIntegerParameterModel */ + CwlIntegerParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_integer + * @constant + * @enum {string} + */ + parameter_type?: "cwl_integer"; + }; + /** CwlNullParameterModel */ + CwlNullParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_null + * @constant + * @enum {string} + */ + parameter_type?: "cwl_null"; + }; + /** CwlStringParameterModel */ + CwlStringParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_string + * @constant + * @enum {string} + */ + parameter_type?: "cwl_string"; + }; + /** CwlUnionParameterModel */ + CwlUnionParameterModel: { + /** Name */ + name: string; + /** + * Parameter Type + * @default cwl_union + * @constant + * @enum {string} + */ + parameter_type?: "cwl_union"; + /** Parameters */ + parameters: ( + | components["schemas"]["CwlIntegerParameterModel"] + | components["schemas"]["CwlFloatParameterModel"] + | components["schemas"]["CwlStringParameterModel"] + | components["schemas"]["CwlBooleanParameterModel"] + | components["schemas"]["CwlNullParameterModel"] + | components["schemas"]["CwlFileParameterModel"] + | components["schemas"]["CwlDirectoryParameterModel"] + | components["schemas"]["CwlUnionParameterModel"] + )[]; + }; /** * DCESummary * @description Dataset Collection Element summary information. @@ -3798,6 +4133,53 @@ export interface components { */ populated?: boolean; }; + /** DataCollectionParameterModel */ + DataCollectionParameterModel: { + /** Argument */ + argument?: string | null; + /** Collection Type */ + collection_type?: string | null; + /** + * Extensions + * @default [ + * "data" + * ] + */ + extensions?: string[]; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_data_collection + * @constant + * @enum {string} + */ + parameter_type?: "gx_data_collection"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** DataElementsFromTarget */ DataElementsFromTarget: { /** @@ -3854,6 +4236,60 @@ export interface components { * @enum {string} */ DataItemSourceType: "hda" | "ldda" | "hdca" | "dce" | "dc"; + /** DataParameterModel */ + DataParameterModel: { + /** Argument */ + argument?: string | null; + /** + * Extensions + * @default [ + * "data" + * ] + */ + extensions?: string[]; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Max */ + max?: number | null; + /** Min */ + min?: number | null; + /** + * Multiple + * @default false + */ + multiple?: boolean; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_data + * @constant + * @enum {string} + */ + parameter_type?: "gx_data"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** DatasetAssociationRoles */ DatasetAssociationRoles: { /** @@ -4480,6 +4916,45 @@ export interface components { */ username: string; }; + /** DirectoryUriParameterModel */ + DirectoryUriParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @constant + * @enum {string} + */ + parameter_type: "gx_directory_uri"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Value */ + value: string | null; + }; /** DisconnectAction */ DisconnectAction: { /** @@ -5279,6 +5754,50 @@ export interface components { /** Step */ step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; }; + /** FloatParameterModel */ + FloatParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Max */ + max?: number | null; + /** Min */ + min?: number | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_float + * @constant + * @enum {string} + */ + parameter_type?: "gx_float"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Value */ + value?: number | null; + }; /** FolderLibraryFolderItem */ FolderLibraryFolderItem: { /** Can Manage */ @@ -6969,6 +7488,44 @@ export interface components { HelpForumUser: { [key: string]: unknown | undefined; }; + /** HiddenParameterModel */ + HiddenParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_hidden + * @constant + * @enum {string} + */ + parameter_type?: "gx_hidden"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** * HistoryActiveContentCounts * @description Contains the number of active, deleted or hidden items in a History. @@ -7630,6 +8187,47 @@ export interface components { /** Uninstalled */ uninstalled: boolean; }; + /** IntegerParameterModel */ + IntegerParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Max */ + max?: number | null; + /** Min */ + min?: number | null; + /** Name */ + name: string; + /** Optional */ + optional: boolean; + /** + * Parameter Type + * @default gx_integer + * @constant + * @enum {string} + */ + parameter_type?: "gx_integer"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Value */ + value?: number | null; + }; /** InvocationCancellationHistoryDeletedResponse */ InvocationCancellationHistoryDeletedResponse: { /** @@ -8517,6 +9115,15 @@ export interface components { */ update_time: string; }; + /** JobCreateResponse */ + JobCreateResponse: { + task_result: components["schemas"]["AsyncTaskResultSummary"]; + /** + * Tool Request Id + * @example 0123456789ABCDEF + */ + tool_request_id: string; + }; /** JobDestinationParams */ JobDestinationParams: { /** @@ -8779,18 +9386,31 @@ export interface components { * Dataset * @description The associated dataset. */ - value: components["schemas"]["EncodedDataItemSourceId"]; + value: components["schemas"]["EncodedDataItemSourceId"]; + }; + /** JobOutputAssociation */ + JobOutputAssociation: { + /** + * dataset + * @description Reference to the associated item. + */ + dataset: components["schemas"]["EncodedDataItemSourceId"]; + /** + * name + * @description Name of the job output parameter. + */ + name: string; }; - /** JobOutputAssociation */ - JobOutputAssociation: { + /** JobOutputCollectionAssociation */ + JobOutputCollectionAssociation: { /** - * dataset + * dataset_collection_instance * @description Reference to the associated item. */ - dataset: components["schemas"]["EncodedDataItemSourceId"]; + dataset_collection_instance: components["schemas"]["EncodedDataItemSourceId"]; /** * name - * @description Name of the job output parameter. + * @description Name of the job parameter. */ name: string; }; @@ -8823,6 +9443,47 @@ export interface components { | string | null; }; + /** JobRequest */ + JobRequest: { + /** + * history_id + * @description TODO + */ + history_id?: string | null; + /** + * Inputs + * @description TODO + */ + inputs?: Record | null; + /** + * rerun_remap_job_id + * @description TODO + */ + rerun_remap_job_id?: string | null; + /** + * Send Email Notification + * @description TODO + * @default false + */ + send_email_notification?: boolean; + /** + * tool_id + * @description TODO + */ + tool_id?: string | null; + /** + * tool_uuid + * @description TODO + */ + tool_uuid?: string | null; + /** + * tool_version + * @description TODO + */ + tool_version?: string | null; + /** use_cached_jobs */ + use_cached_jobs?: boolean | null; + }; /** * JobSourceType * @description Available types of job sources (model classes) that produce dataset collections. @@ -8957,6 +9618,15 @@ export interface components { */ user_email?: string | null; }; + /** LabelValue */ + LabelValue: { + /** Label */ + label: string; + /** Selected */ + selected: boolean; + /** Value */ + value: string; + }; /** * LabelValuePair * @description Generic Label/Value pair model. @@ -10886,6 +11556,68 @@ export interface components { */ action_type: "remove_unlabeled_workflow_outputs"; }; + /** RepeatParameterModel */ + RepeatParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_repeat + * @constant + * @enum {string} + */ + parameter_type?: "gx_repeat"; + /** Parameters */ + parameters: ( + | components["schemas"]["CwlIntegerParameterModel"] + | components["schemas"]["CwlFloatParameterModel"] + | components["schemas"]["CwlStringParameterModel"] + | components["schemas"]["CwlBooleanParameterModel"] + | components["schemas"]["CwlNullParameterModel"] + | components["schemas"]["CwlFileParameterModel"] + | components["schemas"]["CwlDirectoryParameterModel"] + | components["schemas"]["CwlUnionParameterModel"] + | components["schemas"]["TextParameterModel"] + | components["schemas"]["IntegerParameterModel"] + | components["schemas"]["FloatParameterModel"] + | components["schemas"]["BooleanParameterModel"] + | components["schemas"]["HiddenParameterModel"] + | components["schemas"]["SelectParameterModel"] + | components["schemas"]["DataParameterModel"] + | components["schemas"]["DataCollectionParameterModel"] + | components["schemas"]["DirectoryUriParameterModel"] + | components["schemas"]["RulesParameterModel"] + | components["schemas"]["ColorParameterModel"] + | components["schemas"]["ConditionalParameterModel"] + | components["schemas"]["RepeatParameterModel"] + )[]; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** Report */ Report: { /** Markdown */ @@ -10993,6 +11725,44 @@ export interface components { RootModel_Dict_str__int__: { [key: string]: number | undefined; }; + /** RulesParameterModel */ + RulesParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_rules + * @constant + * @enum {string} + */ + parameter_type?: "gx_rules"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** SearchJobsPayload */ SearchJobsPayload: { /** @@ -11012,6 +11782,48 @@ export interface components { tool_id: string; [key: string]: unknown | undefined; }; + /** SelectParameterModel */ + SelectParameterModel: { + /** Argument */ + argument?: string | null; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Multiple */ + multiple: boolean; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** Options */ + options?: components["schemas"]["LabelValue"][] | null; + /** + * Parameter Type + * @default gx_select + * @constant + * @enum {string} + */ + parameter_type?: "gx_select"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + }; /** ServerDirElement */ ServerDirElement: { /** Md5 */ @@ -11996,6 +12808,56 @@ export interface components { */ type: "string"; }; + /** TextParameterModel */ + TextParameterModel: { + /** + * Area + * @default false + */ + area?: boolean; + /** Argument */ + argument?: string | null; + /** + * Default Options + * @default [] + */ + default_options?: components["schemas"]["LabelValue"][]; + /** Help */ + help?: string | null; + /** + * Hidden + * @default false + */ + hidden?: boolean; + /** + * Is Dynamic + * @default false + */ + is_dynamic?: boolean; + /** Label */ + label?: string | null; + /** Name */ + name: string; + /** + * Optional + * @default false + */ + optional?: boolean; + /** + * Parameter Type + * @default gx_text + * @constant + * @enum {string} + */ + parameter_type?: "gx_text"; + /** + * Refresh On Change + * @default false + */ + refresh_on_change?: boolean; + /** Value */ + value?: string | null; + }; /** ToolDataDetails */ ToolDataDetails: { /** @@ -12076,6 +12938,21 @@ export interface components { */ values: string; }; + /** ToolRequestModel */ + ToolRequestModel: { + /** + * ID + * @description Encoded ID of the role + * @example 0123456789ABCDEF + */ + id: string; + /** Request */ + request: Record; + /** State */ + state: string; + /** State Message */ + state_message: string | null; + }; /** ToolStep */ ToolStep: { /** @@ -18993,6 +19870,33 @@ export interface operations { }; }; }; + tool_requests_api_histories__history_id__tool_requests_get: { + /** Return all the tool requests for the tools submitted to this history. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The encoded database identifier of the History. */ + path: { + history_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ToolRequestModel"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; unpublish_api_histories__history_id__unpublish_put: { /** * Removes this item from the published list. @@ -19558,6 +20462,7 @@ export interface operations { /** @description Limit listing of jobs to those that match the specified workflow ID. If none, jobs from any workflow (or from no workflows) may be returned. */ /** @description Limit listing of jobs to those that match the specified workflow invocation ID. If none, jobs from any workflow invocation (or from no workflows) may be returned. */ /** @description Limit listing of jobs to those that match the specified implicit collection job ID. If none, jobs from any implicit collection execution (or from no implicit collection execution) may be returned. */ + /** @description Limit listing of jobs to those that were created from the supplied tool request ID. If none, jobs from any tool request (or from no workflows) may be returned. */ /** @description Sort results by specified field. */ /** * @description A mix of free text and GitHub-style tags used to filter the index operation. @@ -19610,6 +20515,7 @@ export interface operations { workflow_id?: string | null; invocation_id?: string | null; implicit_collection_jobs_id?: string | null; + tool_request_id?: string | null; order_by?: components["schemas"]["JobIndexSortByEnum"]; search?: string | null; limit?: number; @@ -19642,6 +20548,34 @@ export interface operations { }; }; }; + create_api_jobs_post: { + /** Create */ + parameters?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["JobRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["JobCreateResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; search_jobs_api_jobs_search_post: { /** * Return jobs for current user @@ -19938,7 +20872,10 @@ export interface operations { /** @description Successful Response */ 200: { content: { - "application/json": components["schemas"]["JobOutputAssociation"][]; + "application/json": ( + | components["schemas"]["JobOutputAssociation"] + | components["schemas"]["JobOutputCollectionAssociation"] + )[]; }; }; /** @description Validation Error */ @@ -22600,6 +23537,32 @@ export interface operations { }; }; }; + tool_request_state_api_tool_requests__id__state_get: { + /** Get tool request state. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": string; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; index_api_tool_shed_repositories_get: { /** Lists installed tool shed repositories. */ parameters?: { @@ -22708,6 +23671,58 @@ export interface operations { }; }; }; + tool_inputs_api_tools__tool_id__inputs_get: { + /** Get tool inputs. */ + parameters: { + query?: { + tool_version?: string | null; + }; + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The tool ID for the lineage stored in Galaxy's toolbox. */ + path: { + tool_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": ( + | components["schemas"]["CwlIntegerParameterModel"] + | components["schemas"]["CwlFloatParameterModel"] + | components["schemas"]["CwlStringParameterModel"] + | components["schemas"]["CwlBooleanParameterModel"] + | components["schemas"]["CwlNullParameterModel"] + | components["schemas"]["CwlFileParameterModel"] + | components["schemas"]["CwlDirectoryParameterModel"] + | components["schemas"]["CwlUnionParameterModel"] + | components["schemas"]["TextParameterModel"] + | components["schemas"]["IntegerParameterModel"] + | components["schemas"]["FloatParameterModel"] + | components["schemas"]["BooleanParameterModel"] + | components["schemas"]["HiddenParameterModel"] + | components["schemas"]["SelectParameterModel"] + | components["schemas"]["DataParameterModel"] + | components["schemas"]["DataCollectionParameterModel"] + | components["schemas"]["DirectoryUriParameterModel"] + | components["schemas"]["RulesParameterModel"] + | components["schemas"]["ColorParameterModel"] + | components["schemas"]["ConditionalParameterModel"] + | components["schemas"]["RepeatParameterModel"] + )[]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; index_api_tours_get: { /** * Index diff --git a/lib/galaxy/config/schemas/tool_shed_config_schema.yml b/lib/galaxy/config/schemas/tool_shed_config_schema.yml index 2a1eee2b533f..b7b6bfee049e 100644 --- a/lib/galaxy/config/schemas/tool_shed_config_schema.yml +++ b/lib/galaxy/config/schemas/tool_shed_config_schema.yml @@ -102,12 +102,12 @@ mapping: the repositories and tools within the Tool Shed given that you specify the following two config options. - tool_state_cache_dir: + model_cache_dir: type: str - default: database/tool_state_cache + default: database/model_cache required: false desc: | - Cache directory for tool state. + Cache directory for Pydantic model objects. repo_name_boost: type: float diff --git a/lib/galaxy/tool_util/parameters/state.py b/lib/galaxy/tool_util/parameters/state.py index 52a929a19383..b71a57992e42 100644 --- a/lib/galaxy/tool_util/parameters/state.py +++ b/lib/galaxy/tool_util/parameters/state.py @@ -5,8 +5,10 @@ from typing import ( Any, Dict, + List, Optional, Type, + Union, ) from pydantic import BaseModel @@ -17,9 +19,13 @@ create_request_model, StateRepresentationT, ToolParameterBundle, + ToolParameterBundleModel, + ToolParameterT, validate_against_model, ) +HasToolParameters = Union[List[ToolParameterT], ToolParameterBundle] + class ToolState(ABC): input_state: Dict[str, Any] @@ -30,7 +36,7 @@ def __init__(self, input_state: Dict[str, Any]): def _validate(self, pydantic_model: Type[BaseModel]) -> None: validate_against_model(pydantic_model, self.input_state) - def validate(self, input_models: ToolParameterBundle) -> None: + def validate(self, input_models: HasToolParameters) -> None: base_model = self.parameter_model_for(input_models) if base_model is None: raise NotImplementedError( @@ -44,15 +50,20 @@ def state_representation(self) -> StateRepresentationT: """Get state representation of the inputs.""" @classmethod - def parameter_model_for(cls, input_models: ToolParameterBundle) -> Optional[Type[BaseModel]]: - return None + def parameter_model_for(cls, input_models: HasToolParameters) -> Optional[Type[BaseModel]]: + bundle: ToolParameterBundle + if isinstance(input_models, list): + bundle = ToolParameterBundleModel(input_models=input_models) + else: + bundle = input_models + return cls._parameter_model_for(bundle) class RequestToolState(ToolState): state_representation: Literal["request"] = "request" @classmethod - def parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: + def _parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: return create_request_model(input_models) @@ -60,7 +71,7 @@ class RequestInternalToolState(ToolState): state_representation: Literal["request_internal"] = "request_internal" @classmethod - def parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: + def _parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: return create_request_internal_model(input_models) @@ -68,7 +79,7 @@ class JobInternalToolState(ToolState): state_representation: Literal["job_internal"] = "job_internal" @classmethod - def parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: + def _parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: # implement a job model... return create_request_internal_model(input_models) @@ -77,6 +88,6 @@ class TestCaseToolState(ToolState): state_representation: Literal["test_case"] = "test_case" @classmethod - def parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: + def _parameter_model_for(cls, input_models: ToolParameterBundle) -> Type[BaseModel]: # implement a test case model... return create_request_internal_model(input_models) diff --git a/lib/tool_shed/managers/model_cache.py b/lib/tool_shed/managers/model_cache.py new file mode 100644 index 000000000000..9ce8c205e901 --- /dev/null +++ b/lib/tool_shed/managers/model_cache.py @@ -0,0 +1,64 @@ +import json +import os +from typing import ( + Any, + Dict, + Optional, + Type, + TypeVar, +) + +from pydantic import BaseModel + +from galaxy.util.hash_util import md5_hash_str + +RAW_CACHED_JSON = Dict[str, Any] + + +def hash_model(model_class: Type[BaseModel]) -> str: + return md5_hash_str(json.dumps(model_class.model_json_schema())) + + +MODEL_HASHES: Dict[Type[BaseModel], str] = {} + + +M = TypeVar("M", bound=BaseModel) + + +def ensure_model_has_hash(model_class: Type[BaseModel]) -> None: + if model_class not in MODEL_HASHES: + MODEL_HASHES[model_class] = hash_model(model_class) + + +class ModelCache: + _cache_directory: str + + def __init__(self, cache_directory: str): + if not os.path.exists(cache_directory): + os.makedirs(cache_directory) + self._cache_directory = cache_directory + + def _cache_target(self, model_class: Type[M], tool_id: str, tool_version: str) -> str: + ensure_model_has_hash(model_class) + # consider breaking this into multiple directories... + cache_target = os.path.join(self._cache_directory, MODEL_HASHES[model_class], tool_id, tool_version) + return cache_target + + def get_cache_entry_for(self, model_class: Type[M], tool_id: str, tool_version: str) -> Optional[M]: + cache_target = self._cache_target(model_class, tool_id, tool_version) + if not os.path.exists(cache_target): + return None + with open(cache_target) as f: + return model_class.model_validate(json.load(f)) + + def has_cached_entry_for(self, model_class: Type[M], tool_id: str, tool_version: str) -> bool: + cache_target = self._cache_target(model_class, tool_id, tool_version) + return os.path.exists(cache_target) + + def insert_cache_entry_for(self, model_object: M, tool_id: str, tool_version: str) -> None: + cache_target = self._cache_target(model_object.__class__, tool_id, tool_version) + parent_directory = os.path.dirname(cache_target) + if not os.path.exists(parent_directory): + os.makedirs(parent_directory) + with open(cache_target, "w") as f: + json.dump(model_object.dict(), f) diff --git a/lib/tool_shed/managers/tool_state_cache.py b/lib/tool_shed/managers/tool_state_cache.py deleted file mode 100644 index 010ab288a334..000000000000 --- a/lib/tool_shed/managers/tool_state_cache.py +++ /dev/null @@ -1,42 +0,0 @@ -import json -import os -from typing import ( - Any, - Dict, - Optional, -) - -RAW_CACHED_JSON = Dict[str, Any] - - -class ToolStateCache: - _cache_directory: str - - def __init__(self, cache_directory: str): - if not os.path.exists(cache_directory): - os.makedirs(cache_directory) - self._cache_directory = cache_directory - - def _cache_target(self, tool_id: str, tool_version: str): - # consider breaking this into multiple directories... - cache_target = os.path.join(self._cache_directory, tool_id, tool_version) - return cache_target - - def get_cache_entry_for(self, tool_id: str, tool_version: str) -> Optional[RAW_CACHED_JSON]: - cache_target = self._cache_target(tool_id, tool_version) - if not os.path.exists(cache_target): - return None - with open(cache_target) as f: - return json.load(f) - - def has_cached_entry_for(self, tool_id: str, tool_version: str) -> bool: - cache_target = self._cache_target(tool_id, tool_version) - return os.path.exists(cache_target) - - def insert_cache_entry_for(self, tool_id: str, tool_version: str, entry: RAW_CACHED_JSON) -> None: - cache_target = self._cache_target(tool_id, tool_version) - parent_directory = os.path.dirname(cache_target) - if not os.path.exists(parent_directory): - os.makedirs(parent_directory) - with open(cache_target, "w") as f: - json.dump(entry, f) diff --git a/lib/tool_shed/managers/tools.py b/lib/tool_shed/managers/tools.py index a881c13f046d..6a5926068dea 100644 --- a/lib/tool_shed/managers/tools.py +++ b/lib/tool_shed/managers/tools.py @@ -8,6 +8,8 @@ Tuple, ) +from pydantic import BaseModel + from galaxy import exceptions from galaxy.exceptions import ( InternalServerError, @@ -21,13 +23,16 @@ ) from galaxy.tool_util.parameters import ( input_models_for_tool_source, - tool_parameter_bundle_from_json, - ToolParameterBundleModel, + ToolParameterT, ) from galaxy.tool_util.parser import ( get_tool_source, ToolSource, ) +from galaxy.tool_util.parser.interface import ( + Citation, + XrefDict, +) from galaxy.tools.stock import stock_tool_sources from tool_shed.context import ( ProvidesRepositoriesContext, @@ -41,6 +46,50 @@ STOCK_TOOL_SOURCES: Optional[Dict[str, Dict[str, ToolSource]]] = None +# parse the tool source with galaxy.util abstractions to provide a bit richer +# information about the tool than older tool shed abstractions. +class ParsedTool(BaseModel): + id: str + version: Optional[str] + name: str + description: Optional[str] + inputs: List[ToolParameterT] + citations: List[Citation] + license: Optional[str] + profile: Optional[str] + edam_operations: List[str] + edam_topics: List[str] + xrefs: List[XrefDict] + + +def _parse_tool(tool_source: ToolSource) -> ParsedTool: + id = tool_source.parse_id() + version = tool_source.parse_version() + name = tool_source.parse_name() + description = tool_source.parse_description() + inputs = input_models_for_tool_source(tool_source).input_models + citations = tool_source.parse_citations() + license = tool_source.parse_license() + profile = tool_source.parse_profile() + edam_operations = tool_source.parse_edam_operations() + edam_topics = tool_source.parse_edam_topics() + xrefs = tool_source.parse_xrefs() + + return ParsedTool( + id=id, + version=version, + name=name, + description=description, + profile=profile, + inputs=inputs, + license=license, + citations=citations, + edam_operations=edam_operations, + edam_topics=edam_topics, + xrefs=xrefs, + ) + + def search(trans: SessionRequestContext, q: str, page: int = 1, page_size: int = 10) -> dict: """ Perform the search over TS tools index. @@ -97,23 +146,23 @@ def get_repository_metadata_tool_dict( raise ObjectNotFound() -def tool_input_models_cached_for( +def parsed_tool_model_cached_for( trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None -) -> ToolParameterBundleModel: - tool_state_cache = trans.app.tool_state_cache - raw_json = tool_state_cache.get_cache_entry_for(trs_tool_id, tool_version) - if raw_json is not None: - return tool_parameter_bundle_from_json(raw_json) - bundle = tool_input_models_for(trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url) - tool_state_cache.insert_cache_entry_for(trs_tool_id, tool_version, bundle.dict()) - return bundle +) -> ParsedTool: + model_cache = trans.app.model_cache + parsed_tool = model_cache.get_cache_entry_for(ParsedTool, trs_tool_id, tool_version) + if parsed_tool is not None: + return parsed_tool + parsed_tool = parsed_tool_model_for(trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url) + model_cache.insert_cache_entry_for(parsed_tool, trs_tool_id, tool_version) + return parsed_tool -def tool_input_models_for( +def parsed_tool_model_for( trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None -) -> ToolParameterBundleModel: +) -> ParsedTool: tool_source = tool_source_for(trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url) - return input_models_for_tool_source(tool_source) + return _parse_tool(tool_source) def tool_source_for( diff --git a/lib/tool_shed/structured_app.py b/lib/tool_shed/structured_app.py index c3eee0c94299..8fd828f9f5e1 100644 --- a/lib/tool_shed/structured_app.py +++ b/lib/tool_shed/structured_app.py @@ -3,7 +3,7 @@ from galaxy.structured_app import BasicSharedApp if TYPE_CHECKING: - from tool_shed.managers.tool_state_cache import ToolStateCache + from tool_shed.managers.model_cache import ModelCache from tool_shed.repository_registry import Registry as RepositoryRegistry from tool_shed.repository_types.registry import Registry as RepositoryTypesRegistry from tool_shed.util.hgweb_config import HgWebConfigManager @@ -17,4 +17,4 @@ class ToolShedApp(BasicSharedApp): repository_registry: "RepositoryRegistry" hgweb_config_manager: "HgWebConfigManager" security_agent: "CommunityRBACAgent" - tool_state_cache: "ToolStateCache" + model_cache: "ModelCache" diff --git a/lib/tool_shed/webapp/api2/tools.py b/lib/tool_shed/webapp/api2/tools.py index 486a88730909..7f549e80d1c0 100644 --- a/lib/tool_shed/webapp/api2/tools.py +++ b/lib/tool_shed/webapp/api2/tools.py @@ -10,12 +10,12 @@ from galaxy.tool_util.parameters import ( RequestToolState, to_json_schema_string, - ToolParameterBundleModel, ) from tool_shed.context import SessionRequestContext from tool_shed.managers.tools import ( + parsed_tool_model_cached_for, + ParsedTool, search, - tool_input_models_cached_for, ) from tool_shed.managers.trs import ( get_tool, @@ -144,17 +144,17 @@ def trs_get_versions( return get_tool(trans, tool_id).versions @router.get( - "/api/tools/{tool_id}/versions/{tool_version}/parameter_model", + "/api/tools/{tool_id}/versions/{tool_version}", operation_id="tools__parameter_model", summary="Return Galaxy's meta model description of the tool's inputs", ) - def tool_parameters_meta_model( + def show_tool( self, trans: SessionRequestContext = DependsOnTrans, tool_id: str = TOOL_ID_PATH_PARAM, tool_version: str = TOOL_VERSION_PATH_PARAM, - ) -> ToolParameterBundleModel: - return tool_input_models_cached_for(trans, tool_id, tool_version) + ) -> ParsedTool: + return parsed_tool_model_cached_for(trans, tool_id, tool_version) @router.get( "/api/tools/{tool_id}/versions/{tool_version}/parameter_request_schema", @@ -168,6 +168,5 @@ def tool_state( tool_id: str = TOOL_ID_PATH_PARAM, tool_version: str = TOOL_VERSION_PATH_PARAM, ) -> Response: - return json_schema_response( - RequestToolState.parameter_model_for(tool_input_models_cached_for(trans, tool_id, tool_version)) - ) + parsed_tool = parsed_tool_model_cached_for(trans, tool_id, tool_version) + return json_schema_response(RequestToolState.parameter_model_for(parsed_tool.inputs)) diff --git a/lib/tool_shed/webapp/app.py b/lib/tool_shed/webapp/app.py index 4083674241a4..e046301497ad 100644 --- a/lib/tool_shed/webapp/app.py +++ b/lib/tool_shed/webapp/app.py @@ -33,7 +33,7 @@ from galaxy.structured_app import BasicSharedApp from galaxy.web_stack import application_stack_instance from tool_shed.grids.repository_grid_filter_manager import RepositoryGridFilterManager -from tool_shed.managers.tool_state_cache import ToolStateCache +from tool_shed.managers.model_cache import ModelCache from tool_shed.structured_app import ToolShedApp from tool_shed.util.hgweb_config import hgweb_config_manager from tool_shed.webapp.model.migrations import verify_database @@ -84,7 +84,7 @@ def __init__(self, **kwd) -> None: self._register_singleton(SharedModelMapping, model) self._register_singleton(mapping.ToolShedModelMapping, model) self._register_singleton(scoped_session, self.model.context) - self.tool_state_cache = ToolStateCache(self.config.tool_state_cache_dir) + self.model_cache = ModelCache(self.config.model_cache_dir) self.user_manager = self._register_singleton(UserManager, UserManager(self, app_type="tool_shed")) self.api_keys_manager = self._register_singleton(ApiKeyManager) # initialize the Tool Shed tag handler. diff --git a/lib/tool_shed/webapp/frontend/src/schema/schema.ts b/lib/tool_shed/webapp/frontend/src/schema/schema.ts index 547f47f9e0be..713faca2bcf9 100644 --- a/lib/tool_shed/webapp/frontend/src/schema/schema.ts +++ b/lib/tool_shed/webapp/frontend/src/schema/schema.ts @@ -168,7 +168,7 @@ export interface paths { */ put: operations["tools__build_search_index"] } - "/api/tools/{tool_id}/versions/{tool_version}/parameter_model": { + "/api/tools/{tool_id}/versions/{tool_version}": { /** Return Galaxy's meta model description of the tool's inputs */ get: operations["tools__parameter_model"] } @@ -353,6 +353,13 @@ export interface components { */ type: string } + /** Citation */ + Citation: { + /** Content */ + content: string + /** Type */ + type: string + } /** ColorParameterModel */ ColorParameterModel: { /** Argument */ @@ -1042,6 +1049,53 @@ export interface components { */ url: string } + /** ParsedTool */ + ParsedTool: { + /** Citations */ + citations: components["schemas"]["Citation"][] + /** Description */ + description: string | null + /** Edam Operations */ + edam_operations: string[] + /** Edam Topics */ + edam_topics: string[] + /** Id */ + id: string + /** Inputs */ + inputs: ( + | components["schemas"]["CwlIntegerParameterModel"] + | components["schemas"]["CwlFloatParameterModel"] + | components["schemas"]["CwlStringParameterModel"] + | components["schemas"]["CwlBooleanParameterModel"] + | components["schemas"]["CwlNullParameterModel"] + | components["schemas"]["CwlFileParameterModel"] + | components["schemas"]["CwlDirectoryParameterModel"] + | components["schemas"]["CwlUnionParameterModel"] + | components["schemas"]["TextParameterModel"] + | components["schemas"]["IntegerParameterModel"] + | components["schemas"]["FloatParameterModel"] + | components["schemas"]["BooleanParameterModel"] + | components["schemas"]["HiddenParameterModel"] + | components["schemas"]["SelectParameterModel"] + | components["schemas"]["DataParameterModel"] + | components["schemas"]["DataCollectionParameterModel"] + | components["schemas"]["DirectoryUriParameterModel"] + | components["schemas"]["RulesParameterModel"] + | components["schemas"]["ColorParameterModel"] + | components["schemas"]["ConditionalParameterModel"] + | components["schemas"]["RepeatParameterModel"] + )[] + /** License */ + license: string | null + /** Name */ + name: string + /** Profile */ + profile: string | null + /** Version */ + version: string | null + /** Xrefs */ + xrefs: components["schemas"]["XrefDict"][] + } /** RepeatParameterModel */ RepeatParameterModel: { /** Argument */ @@ -1633,33 +1687,6 @@ export interface components { */ name?: string | null } - /** ToolParameterBundleModel */ - ToolParameterBundleModel: { - /** Input Models */ - input_models: ( - | components["schemas"]["CwlIntegerParameterModel"] - | components["schemas"]["CwlFloatParameterModel"] - | components["schemas"]["CwlStringParameterModel"] - | components["schemas"]["CwlBooleanParameterModel"] - | components["schemas"]["CwlNullParameterModel"] - | components["schemas"]["CwlFileParameterModel"] - | components["schemas"]["CwlDirectoryParameterModel"] - | components["schemas"]["CwlUnionParameterModel"] - | components["schemas"]["TextParameterModel"] - | components["schemas"]["IntegerParameterModel"] - | components["schemas"]["FloatParameterModel"] - | components["schemas"]["BooleanParameterModel"] - | components["schemas"]["HiddenParameterModel"] - | components["schemas"]["SelectParameterModel"] - | components["schemas"]["DataParameterModel"] - | components["schemas"]["DataCollectionParameterModel"] - | components["schemas"]["DirectoryUriParameterModel"] - | components["schemas"]["RulesParameterModel"] - | components["schemas"]["ColorParameterModel"] - | components["schemas"]["ConditionalParameterModel"] - | components["schemas"]["RepeatParameterModel"] - )[] - } /** ToolVersion */ ToolVersion: { /** @@ -1853,6 +1880,13 @@ export interface components { /** Version Major */ version_major: string } + /** XrefDict */ + XrefDict: { + /** Reftype */ + reftype: string + /** Value */ + value: string + } } responses: never parameters: never @@ -2605,7 +2639,7 @@ export interface operations { /** @description Successful Response */ 200: { content: { - "application/json": components["schemas"]["ToolParameterBundleModel"] + "application/json": components["schemas"]["ParsedTool"] } } /** @description Validation Error */ diff --git a/test/unit/tool_shed/_util.py b/test/unit/tool_shed/_util.py index 3002c6a82fab..755731b018ad 100644 --- a/test/unit/tool_shed/_util.py +++ b/test/unit/tool_shed/_util.py @@ -18,7 +18,7 @@ from galaxy.util import safe_makedirs from tool_shed.context import ProvidesRepositoriesContext from tool_shed.managers.repositories import upload_tar_and_set_metadata -from tool_shed.managers.tool_state_cache import ToolStateCache +from tool_shed.managers.model_cache import ModelCache from tool_shed.managers.users import create_user from tool_shed.repository_types import util as rt_util from tool_shed.repository_types.registry import Registry as RepositoryTypesRegistry @@ -81,7 +81,7 @@ def __init__(self, temp_directory=None): self.config = TestToolShedConfig(temp_directory) self.security = IdEncodingHelper(id_secret=self.config.id_secret) self.repository_registry = tool_shed.repository_registry.Registry(self) - self.tool_state_cache = ToolStateCache(os.path.join(temp_directory, "tool_state_cache")) + self.model_cache = ModelCache(os.path.join(temp_directory, "model_cache")) @property def security_agent(self): diff --git a/test/unit/tool_shed/test_model_cache.py b/test/unit/tool_shed/test_model_cache.py new file mode 100644 index 000000000000..30dac2a3a620 --- /dev/null +++ b/test/unit/tool_shed/test_model_cache.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel, ConfigDict + +from tool_shed.managers.model_cache import ( + hash_model, + ModelCache, +) + + +class Moo(BaseModel): + foo: int + + +class MooLike(BaseModel): + model_config = ConfigDict(title="Moo") + foo: int + + +class NewMoo(BaseModel): + model_config = ConfigDict(title="Moo") + foo: int + new_prop: str + + +def test_hash(): + hash_moo_1 = hash_model(Moo) + hash_moo_2 = hash_model(Moo) + assert hash_moo_1 == hash_moo_2 + + +def test_hash_by_value(): + hash_moo_1 = hash_model(Moo) + hash_moo_like = hash_model(MooLike) + assert hash_moo_1 == hash_moo_like + + +def test_hash_different_on_updates(): + hash_moo_1 = hash_model(Moo) + hash_moo_new = hash_model(NewMoo) + assert hash_moo_1 != hash_moo_new + + +def cache_dict(tmp_path): + model_cache = ModelCache(tmp_path) + assert not model_cache.has_cached_entry_for(Moo, "moo", "1.0") + assert None is model_cache.get_cache_entry_for(Moo, "moo", "1.0") + model_cache.insert_cache_entry_for(Moo(foo=4), "moo", "1.0") + moo = model_cache.get_cache_entry_for(Moo, "moo", "1.0") + assert moo + assert moo.foo == 4 + assert model_cache.has_cached_entry_for(Moo, "moo", "1.0") diff --git a/test/unit/tool_shed/test_tool_source.py b/test/unit/tool_shed/test_tool_source.py index 4925cd52cd3f..601d4d63df54 100644 --- a/test/unit/tool_shed/test_tool_source.py +++ b/test/unit/tool_shed/test_tool_source.py @@ -1,7 +1,7 @@ from tool_shed.context import ProvidesRepositoriesContext from tool_shed.managers.tools import ( - tool_input_models_cached_for, - tool_input_models_for, + parsed_tool_model_cached_for, + parsed_tool_model_for, tool_source_for, ) from tool_shed.webapp.model import Repository @@ -17,22 +17,22 @@ def test_get_tool(provides_repositories: ProvidesRepositoriesContext, new_reposi repo_path = new_repository.repo_path(app=provides_repositories.app) tool_source = tool_source_for(provides_repositories, encoded_id, "1.2.0", repository_clone_url=repo_path) assert tool_source.parse_id() == "Add_a_column1" - bundle = tool_input_models_for(provides_repositories, encoded_id, "1.2.0", repository_clone_url=repo_path) - assert len(bundle.input_models) == 3 + bundle = parsed_tool_model_for(provides_repositories, encoded_id, "1.2.0", repository_clone_url=repo_path) + assert len(bundle.inputs) == 3 - cached_bundle = tool_input_models_cached_for( + cached_bundle = parsed_tool_model_cached_for( provides_repositories, encoded_id, "1.2.0", repository_clone_url=repo_path ) - assert len(cached_bundle.input_models) == 3 + assert len(cached_bundle.inputs) == 3 - cached_bundle = tool_input_models_cached_for( + cached_bundle = parsed_tool_model_cached_for( provides_repositories, encoded_id, "1.2.0", repository_clone_url=repo_path ) - assert len(cached_bundle.input_models) == 3 + assert len(cached_bundle.inputs) == 3 def test_stock_bundle(provides_repositories: ProvidesRepositoriesContext): - cached_bundle = tool_input_models_cached_for( + cached_bundle = parsed_tool_model_cached_for( provides_repositories, "__ZIP_COLLECTION__", "1.0.0", repository_clone_url=None ) - assert len(cached_bundle.input_models) == 2 + assert len(cached_bundle.inputs) == 2