mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #15106 from mvdbeek/typescript_api_client
Add autogenerated typescript API client & use it
This commit is contained in:
@@ -168,7 +168,10 @@ ifndef YARN
|
||||
else
|
||||
cd client && yarn install $(YARN_INSTALL_OPTS)
|
||||
endif
|
||||
|
||||
|
||||
|
||||
update-client-api-schema: node-deps
|
||||
$(IN_VENV) cd client && python ../scripts/dump_openapi_schema.py | yarn run openapi-typescript --output src/schema/schema.ts && npx prettier --write src/schema/schema.ts
|
||||
|
||||
client: node-deps ## Rebuild client-side artifacts for local development.
|
||||
cd client && yarn run build
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-regexp": "^0.4.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"openapi-typescript": "^6.1.0",
|
||||
"openapi-typescript-fetch": "^1.1.3",
|
||||
"pinia": "^2.0.23",
|
||||
"pinia-plugin-persistedstate": "^2.4.0",
|
||||
"popper.js": "^1.16.1",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Services } from "components/Dataset/services";
|
||||
import { mapCacheActions } from "vuex-cache";
|
||||
import { getCompositeDatasetInfo } from "components/Dataset/services";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
@@ -49,7 +49,6 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.services = new Services();
|
||||
this.pathDestination = {};
|
||||
if (this.path && this.path !== "undefined") {
|
||||
// download individual file from composite dataset
|
||||
@@ -58,7 +57,7 @@ export default {
|
||||
});
|
||||
} else {
|
||||
// download whole dataset
|
||||
this.services.getCompositeDatasetInfo(this.history_dataset_id).then((response) => {
|
||||
getCompositeDatasetInfo(this.history_dataset_id).then((response) => {
|
||||
this.pathDestination = { fileLink: `${response.download_url}?to_ext=${response.file_ext}` };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script>
|
||||
import store from "store";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { Services } from "./services";
|
||||
import { copyDataset, getDatasets, updateTags } from "./services";
|
||||
import DatasetName from "./DatasetName";
|
||||
import DatasetHistory from "./DatasetHistory";
|
||||
import DelayedInput from "components/Common/DelayedInput";
|
||||
@@ -105,21 +105,19 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.loadHistories();
|
||||
this.services = new Services();
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
...mapActions("history", ["loadHistories"]),
|
||||
load(concat = false) {
|
||||
this.loading = true;
|
||||
this.services
|
||||
.getDatasets({
|
||||
query: this.query,
|
||||
sortBy: this.sortBy,
|
||||
sortDesc: this.sortDesc,
|
||||
offset: this.offset,
|
||||
limit: this.limit,
|
||||
})
|
||||
getDatasets({
|
||||
query: this.query,
|
||||
sortBy: this.sortBy,
|
||||
sortDesc: this.sortDesc,
|
||||
offset: this.offset,
|
||||
limit: this.limit,
|
||||
})
|
||||
.then((datasets) => {
|
||||
if (concat) {
|
||||
this.rows = this.rows.concat(datasets);
|
||||
@@ -137,8 +135,7 @@ export default {
|
||||
const history = Galaxy.currHistoryPanel;
|
||||
const dataset_id = item.id;
|
||||
const history_id = history.model.id;
|
||||
this.services
|
||||
.copyDataset(dataset_id, history_id)
|
||||
copyDataset(dataset_id, history_id)
|
||||
.then((response) => {
|
||||
history.loadCurrentHistory();
|
||||
})
|
||||
@@ -157,7 +154,7 @@ export default {
|
||||
onTags(tags, index) {
|
||||
const item = this.rows[index];
|
||||
item.tags = tags;
|
||||
this.services.updateTags(item.id, "HistoryDatasetAssociation", tags).catch((error) => {
|
||||
updateTags(item.id, "HistoryDatasetAssociation", tags).catch((error) => {
|
||||
this.onError(error);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Services } from "components/Dataset/services";
|
||||
import { getCompositeDatasetLink } from "components/Dataset/services";
|
||||
import store from "../../store/index";
|
||||
|
||||
export const getPathDestination = async (history_dataset_id, path) => {
|
||||
const services = new Services();
|
||||
|
||||
const computePathDestination = (pathDestination) => {
|
||||
if (path === undefined || path === "undefined") {
|
||||
return pathDestination;
|
||||
@@ -21,7 +19,7 @@ export const getPathDestination = async (history_dataset_id, path) => {
|
||||
pathDestination.filepath = filepath;
|
||||
return pathDestination;
|
||||
}
|
||||
pathDestination.fileLink = services.getCompositeDatasetLink(history_dataset_id, datasetEntry.path);
|
||||
pathDestination.fileLink = getCompositeDatasetLink(history_dataset_id, datasetEntry.path);
|
||||
}
|
||||
return pathDestination;
|
||||
};
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { safePath } from "utils/redirect";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
|
||||
/** Datasets request helper **/
|
||||
export class Services {
|
||||
async getDatasets(options = {}) {
|
||||
let params = "";
|
||||
if (options.sortBy) {
|
||||
const sortPrefix = options.sortDesc ? "-dsc" : "-asc";
|
||||
params += `order=${options.sortBy}${sortPrefix}&`;
|
||||
}
|
||||
if (options.limit) {
|
||||
params += `limit=${options.limit}&`;
|
||||
}
|
||||
if (options.offset) {
|
||||
params += `offset=${options.offset}&`;
|
||||
}
|
||||
if (options.query) {
|
||||
params += `q=name-contains&qv=${options.query}&`;
|
||||
}
|
||||
const url = `/api/datasets?${params}`;
|
||||
try {
|
||||
const { data } = await axios.get(safePath(url));
|
||||
return data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
async copyDataset(datasetId, historyId, type = "dataset", source = "hda") {
|
||||
const url = `/api/histories/${historyId}/contents`;
|
||||
try {
|
||||
const response = await axios.post(safePath(url), {
|
||||
type: type,
|
||||
source: source,
|
||||
content: datasetId,
|
||||
});
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
async updateTags(itemId, itemClass, itemTags) {
|
||||
const url = `/api/tags`;
|
||||
try {
|
||||
const response = await axios.put(safePath(url), {
|
||||
item_id: itemId,
|
||||
item_class: itemClass,
|
||||
item_tags: itemTags,
|
||||
});
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
|
||||
getCompositeDatasetLink(historyDatasetId, path) {
|
||||
return safePath(`/api/histories/${historyDatasetId}/contents/${historyDatasetId}/display?filename=${path}`);
|
||||
}
|
||||
|
||||
async getCompositeDatasetInfo(id) {
|
||||
const url = `/api/histories/${id}/contents/${id}`;
|
||||
try {
|
||||
const response = await axios.get(safePath(url));
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { fetcher } from "schema";
|
||||
import { safePath } from "utils/redirect";
|
||||
import type { FetchArgType } from "openapi-typescript-fetch";
|
||||
|
||||
const _getDatasets = fetcher.path("/api/datasets").method("get").create();
|
||||
type GetDatasetsApiOptions = FetchArgType<typeof _getDatasets>;
|
||||
type GetDatasetsQuery = Pick<GetDatasetsApiOptions, "limit" | "offset">;
|
||||
// custom interface for how we use getDatasets
|
||||
interface GetDatasetsOptions extends GetDatasetsQuery {
|
||||
sortBy?: string;
|
||||
sortDesc?: string;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
/** Datasets request helper **/
|
||||
export async function getDatasets(options: GetDatasetsOptions = {}) {
|
||||
const params: GetDatasetsApiOptions = {};
|
||||
if (options.sortBy) {
|
||||
const sortPrefix = options.sortDesc ? "-dsc" : "-asc";
|
||||
params.order = `${options.sortBy}${sortPrefix}&`;
|
||||
}
|
||||
if (options.limit) {
|
||||
params.limit = options.limit;
|
||||
}
|
||||
if (options.offset) {
|
||||
params.offset = options.offset;
|
||||
}
|
||||
if (options.query) {
|
||||
params.q = ["name-contains"];
|
||||
params.qv = [options.query];
|
||||
}
|
||||
const { data } = await _getDatasets(params);
|
||||
return data;
|
||||
}
|
||||
|
||||
const _copyDataset = fetcher.path("/api/histories/{history_id}/contents/{type}s").method("post").create();
|
||||
type HistoryContentsArgs = FetchArgType<typeof _copyDataset>;
|
||||
export async function copyDataset(
|
||||
datasetId: HistoryContentsArgs["content"],
|
||||
historyId: HistoryContentsArgs["history_id"],
|
||||
type: HistoryContentsArgs["type"] = "dataset",
|
||||
source: HistoryContentsArgs["source"] = "hda"
|
||||
) {
|
||||
const response = await _copyDataset({
|
||||
history_id: historyId,
|
||||
type,
|
||||
source: source,
|
||||
content: datasetId,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
const _updateTags = fetcher.path("/api/tags").method("put").create();
|
||||
type UpdateTagsArgs = FetchArgType<typeof _updateTags>;
|
||||
export async function updateTags(
|
||||
itemId: UpdateTagsArgs["item_id"],
|
||||
itemClass: UpdateTagsArgs["item_class"],
|
||||
itemTags: UpdateTagsArgs["item_tags"]
|
||||
) {
|
||||
const { data } = await _updateTags({
|
||||
item_id: itemId,
|
||||
item_class: itemClass,
|
||||
item_tags: itemTags,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getCompositeDatasetLink(historyDatasetId: string, path: string) {
|
||||
// TODO: historyDatasetId is wrong here, we should expose the route without forcing to provide a history id
|
||||
return safePath(`/api/histories/${historyDatasetId}/contents/${historyDatasetId}/display?filename=${path}`);
|
||||
}
|
||||
|
||||
const getDataset = fetcher.path("/api/histories/{history_id}/contents/{id}").method("get").create();
|
||||
export async function getCompositeDatasetInfo(id: string) {
|
||||
// TODO: ${id} as history id is wrong here, we should expose the route without forcing to provide a history id
|
||||
const { data } = await getDataset({ history_id: id, id });
|
||||
return data;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { DatatypesMapperModel } from "./model.js";
|
||||
import { DatatypesMapperModel } from "./model";
|
||||
import { getDatatypes } from "./services";
|
||||
|
||||
export async function getDatatypesMapper(upload_only = true) {
|
||||
@@ -1 +1 @@
|
||||
export { getDatatypesMapper } from "./factory.js";
|
||||
export { getDatatypesMapper } from "./factory";
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
|
||||
import { getDatatypesMapper } from "./index";
|
||||
import { getDatatypes } from "./services";
|
||||
import { typesAndMappingResponse } from "./test_fixtures";
|
||||
|
||||
jest.mock("./services");
|
||||
|
||||
describe("Datatypes/index.js", () => {
|
||||
let axiosMock;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
axiosMock.restore();
|
||||
});
|
||||
|
||||
describe("getDatatypesMapper", () => {
|
||||
it("should fetch logic from API for comparing datatypes in a hierarchy", async () => {
|
||||
axiosMock.onGet(`/api/datatypes/types_and_mapping`).reply(200, typesAndMappingResponse);
|
||||
getDatatypes.mockResolvedValue(typesAndMappingResponse);
|
||||
await getDatatypesMapper().then((mapper) => {
|
||||
expect(mapper.isSubType("txt", "data")).toBe(true);
|
||||
expect(mapper.isSubType("txt", "txt")).toBe(true);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export class DatatypesMapperModel {
|
||||
constructor(typesAndMapping) {
|
||||
// create a shallow copy of the datatypes, otherwise sort mutates in place
|
||||
// and causes a possible infinite render update loop.
|
||||
this.datatypes = [...typesAndMapping.datatypes];
|
||||
this.datatypes.sort();
|
||||
this.datatypesMapping = typesAndMapping.datatypes_mapping;
|
||||
}
|
||||
|
||||
isSubType(child, parent) {
|
||||
const mapping = this.datatypesMapping;
|
||||
child = mapping.ext_to_class_name[child];
|
||||
parent = mapping.ext_to_class_name[parent];
|
||||
return mapping.class_to_classes[child] && parent in mapping.class_to_classes[child];
|
||||
}
|
||||
|
||||
isSubTypeOfAny(child, parents) {
|
||||
return parents.some((parent) => this.isSubType(child, parent));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { components } from "schema";
|
||||
|
||||
export type DatatypesCombinedMap = components["schemas"]["DatatypesCombinedMap"];
|
||||
|
||||
export class DatatypesMapperModel {
|
||||
datatypes: DatatypesCombinedMap["datatypes"];
|
||||
datatypesMapping: DatatypesCombinedMap["datatypes_mapping"];
|
||||
|
||||
constructor(typesAndMapping: DatatypesCombinedMap) {
|
||||
// create a shallow copy of the datatypes, otherwise sort mutates in place
|
||||
// and causes a possible infinite render update loop.
|
||||
this.datatypes = [...typesAndMapping.datatypes];
|
||||
this.datatypes.sort();
|
||||
this.datatypesMapping = typesAndMapping.datatypes_mapping;
|
||||
}
|
||||
|
||||
isSubType(child: string, parent: string): boolean {
|
||||
const mapping = this.datatypesMapping;
|
||||
const childClassName = mapping.ext_to_class_name[child];
|
||||
const parentClassName = mapping.ext_to_class_name[parent];
|
||||
if (!childClassName || !parentClassName) {
|
||||
return false;
|
||||
}
|
||||
const childClassMappings = mapping.class_to_classes[childClassName];
|
||||
if (!childClassMappings) {
|
||||
return false;
|
||||
}
|
||||
return parentClassName in childClassMappings;
|
||||
}
|
||||
|
||||
isSubTypeOfAny(child: string, parents: DatatypesCombinedMap["datatypes"]): boolean {
|
||||
return parents.some((parent) => this.isSubType(child, parent));
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
|
||||
export async function getDatatypes(upload_only = true) {
|
||||
try {
|
||||
const request = await axios.get(`${getAppRoot()}api/datatypes/types_and_mapping`, {
|
||||
params: {
|
||||
upload_only: upload_only,
|
||||
},
|
||||
});
|
||||
return request.data;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { fetcher } from "schema/fetcher";
|
||||
|
||||
const getTypesAndMappings = fetcher.path("/api/datatypes/types_and_mapping").method("get").create();
|
||||
|
||||
export async function getDatatypes(upload_only = true) {
|
||||
const response = await getTypesAndMappings({ upload_only });
|
||||
return response.data;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../tests/qunit/test-data/json/datatypes.json
|
||||
@@ -0,0 +1 @@
|
||||
../../../../tests/qunit/test-data/json/datatypes.mapping.json
|
||||
@@ -1,11 +0,0 @@
|
||||
// TODO: Swap these awful relative paths to an alias
|
||||
import DatatypesMappingJson from "qunit/test-data/json/datatypes.mapping.json";
|
||||
import DatatypesJson from "qunit/test-data/json/datatypes.json";
|
||||
import { DatatypesMapperModel } from "./model.js";
|
||||
|
||||
export const typesAndMappingResponse = {
|
||||
datatypes: DatatypesJson,
|
||||
datatypes_mapping: DatatypesMappingJson,
|
||||
};
|
||||
|
||||
export const testDatatypesMapper = new DatatypesMapperModel(typesAndMappingResponse);
|
||||
@@ -0,0 +1,12 @@
|
||||
// TODO: Swap these awful relative paths to an alias
|
||||
import DatatypesMappingJson from "./test-data/datatypes.mapping.json";
|
||||
import DatatypesJson from "./test-data/datatypes.json";
|
||||
import { DatatypesMapperModel } from "./model";
|
||||
import type { DatatypesCombinedMap } from "./model";
|
||||
|
||||
export const typesAndMappingResponse: DatatypesCombinedMap = {
|
||||
datatypes: DatatypesJson,
|
||||
datatypes_mapping: DatatypesMappingJson,
|
||||
};
|
||||
|
||||
export const testDatatypesMapper = new DatatypesMapperModel(typesAndMappingResponse);
|
||||
@@ -166,7 +166,7 @@ import HistorySelectionStatus from "./HistoryOperations/SelectionStatus";
|
||||
import SelectionChangeWarning from "./HistoryOperations/SelectionChangeWarning";
|
||||
import OperationErrorDialog from "./HistoryOperations/OperationErrorDialog";
|
||||
import { rewatchHistory } from "store/historyStore/model/watchHistory";
|
||||
import { Services as DatasetServices } from "components/Dataset/services";
|
||||
import { copyDataset } from "components/Dataset/services";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -254,9 +254,6 @@ export default {
|
||||
this.filterText = newVal;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.datasetServices = new DatasetServices();
|
||||
},
|
||||
methods: {
|
||||
...mapActions("history", ["loadHistoryById"]),
|
||||
getHighlight(item) {
|
||||
@@ -338,8 +335,7 @@ export default {
|
||||
const data = JSON.parse(evt.dataTransfer.getData("text"))[0];
|
||||
const dataSource = data.history_content_type === "dataset" ? "hda" : "hdca";
|
||||
if (data.history_id != this.historyId) {
|
||||
this.datasetServices
|
||||
.copyDataset(data.id, this.historyId, data.history_content_type, dataSource)
|
||||
copyDataset(data.id, this.historyId, data.history_content_type, dataSource)
|
||||
.then(() => {
|
||||
if (data.history_content_type === "dataset") {
|
||||
Toast.info("Dataset copied to history");
|
||||
|
||||
@@ -68,7 +68,7 @@ export function get() {
|
||||
*
|
||||
* @param {*} defaultRoot
|
||||
*/
|
||||
export function getAppRoot(defaultRoot = "/") {
|
||||
export function getAppRoot(defaultRoot = "/", stripTrailingSlash = false) {
|
||||
let root = defaultRoot;
|
||||
try {
|
||||
// try actual config
|
||||
@@ -80,5 +80,8 @@ export function getAppRoot(defaultRoot = "/") {
|
||||
console.warn("Unable to find index link in head", err);
|
||||
}
|
||||
}
|
||||
if (stripTrailingSlash) {
|
||||
root = (root || "").replace(/\/$/, "");
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Fetcher } from "openapi-typescript-fetch";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { rethrowSimple } from "utils/simple-error";
|
||||
import type { paths } from "./schema";
|
||||
import type { Middleware } from "openapi-typescript-fetch";
|
||||
|
||||
const rethrowSimpleMiddleware: Middleware = async (url, init, next) => {
|
||||
try {
|
||||
const response = await next(url, init);
|
||||
return response;
|
||||
} catch (e) {
|
||||
rethrowSimple(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const fetcher = Fetcher.for<paths>();
|
||||
fetcher.configure({ baseUrl: getAppRoot(undefined, true), use: [rethrowSimpleMiddleware] });
|
||||
@@ -0,0 +1,2 @@
|
||||
export type { components, operations, paths } from "./schema";
|
||||
export { fetcher } from "./fetcher";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
export function errorMessageAsString(e, defaultMessage = "Request failed.") {
|
||||
export function errorMessageAsString(e: any, defaultMessage = "Request failed.") {
|
||||
let message = defaultMessage;
|
||||
if (e && e.response && e.response.data && e.response.data.err_msg) {
|
||||
message = e.response.data.err_msg;
|
||||
@@ -10,7 +10,7 @@ export function errorMessageAsString(e, defaultMessage = "Request failed.") {
|
||||
return message;
|
||||
}
|
||||
|
||||
export function rethrowSimple(e) {
|
||||
export function rethrowSimple(e: any): never {
|
||||
console.debug(e);
|
||||
throw errorMessageAsString(e);
|
||||
}
|
||||
+58
-1
@@ -2495,6 +2495,11 @@ ansi-colors@^1.0.1:
|
||||
dependencies:
|
||||
ansi-wrap "^0.1.0"
|
||||
|
||||
ansi-colors@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
|
||||
integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
|
||||
|
||||
ansi-escapes@^4.2.1:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
||||
@@ -3154,6 +3159,13 @@ buffer@^6.0.3:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
busboy@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
|
||||
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
|
||||
dependencies:
|
||||
streamsearch "^1.1.0"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
@@ -4820,6 +4832,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-glob@^3.2.12:
|
||||
version "3.2.12"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-glob@^3.2.9:
|
||||
version "3.2.11"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
||||
@@ -7850,6 +7873,23 @@ open@^8.0.9:
|
||||
is-docker "^2.1.1"
|
||||
is-wsl "^2.2.0"
|
||||
|
||||
openapi-typescript-fetch@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/openapi-typescript-fetch/-/openapi-typescript-fetch-1.1.3.tgz#354e6803b7885c7142ae11adc1b758afafd8aa2e"
|
||||
integrity sha512-smLZPck4OkKMNExcw8jMgrMOGgVGx2N/s6DbKL2ftNl77g5HfoGpZGFy79RBzU/EkaO0OZpwBnslfdBfh7ZcWg==
|
||||
|
||||
openapi-typescript@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-6.1.0.tgz#404d94ea8598f757c526f9a414e66ba96c050dad"
|
||||
integrity sha512-4G0hQu9zXHESZe4fZouucmA+6J5oKSBNYzG0gPbV9V4aKBNIwfUY86ur/cxyTMzuN9vQuAiMe//q4ZMXgMvGtQ==
|
||||
dependencies:
|
||||
ansi-colors "^4.1.3"
|
||||
fast-glob "^3.2.12"
|
||||
js-yaml "^4.1.0"
|
||||
supports-color "^9.2.3"
|
||||
undici "^5.12.0"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
optionator@^0.8.1:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
||||
@@ -9553,6 +9593,11 @@ streamroller@^3.1.2:
|
||||
debug "^4.3.4"
|
||||
fs-extra "^8.1.0"
|
||||
|
||||
streamsearch@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
string-length@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
|
||||
@@ -9703,6 +9748,11 @@ supports-color@^8.0.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^9.2.3:
|
||||
version "9.2.3"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.3.tgz#a6e2c97fc20c80abecd69e50aebe4783ff77d45a"
|
||||
integrity sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==
|
||||
|
||||
supports-hyperlinks@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb"
|
||||
@@ -10137,6 +10187,13 @@ undertaker@^1.2.1:
|
||||
object.reduce "^1.0.0"
|
||||
undertaker-registry "^1.0.0"
|
||||
|
||||
undici@^5.12.0:
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/undici/-/undici-5.13.0.tgz#56772fba89d8b25e39bddc8c26a438bd73ea69bb"
|
||||
integrity sha512-UDZKtwb2k7KRsK4SdXWG7ErXiL7yTGgLWvk2AXO1JMjgjh404nFo6tWSCM2xMpJwMPx3J8i/vfqEh1zOqvj82Q==
|
||||
dependencies:
|
||||
busboy "^1.6.0"
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
|
||||
@@ -10912,7 +10969,7 @@ yargs-parser@^20.2.2:
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||
|
||||
yargs-parser@^21.0.1:
|
||||
yargs-parser@^21.0.1, yargs-parser@^21.1.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
)
|
||||
|
||||
from a2wsgi import WSGIMiddleware
|
||||
from fastapi import (
|
||||
FastAPI,
|
||||
@@ -142,12 +147,31 @@ def include_legacy_openapi(app, gx_app):
|
||||
return app.openapi_schema
|
||||
|
||||
|
||||
def initialize_fast_app(gx_wsgi_webapp, gx_app):
|
||||
app = FastAPI(
|
||||
def get_fastapi_instance() -> FastAPI:
|
||||
return FastAPI(
|
||||
title="Galaxy API",
|
||||
docs_url="/api/docs",
|
||||
openapi_tags=api_tags_metadata,
|
||||
)
|
||||
|
||||
|
||||
def get_openapi_schema() -> Dict[str, Any]:
|
||||
"""
|
||||
Dumps openAPI schema without starting a full app and webserver.
|
||||
"""
|
||||
app = get_fastapi_instance()
|
||||
include_all_package_routers(app, "galaxy.webapps.galaxy.api")
|
||||
return get_openapi(
|
||||
title=app.title,
|
||||
version=app.version,
|
||||
openapi_version=app.openapi_version,
|
||||
description=app.description,
|
||||
routes=app.routes,
|
||||
)
|
||||
|
||||
|
||||
def initialize_fast_app(gx_wsgi_webapp, gx_app):
|
||||
app = get_fastapi_instance()
|
||||
add_exception_handler(app)
|
||||
add_galaxy_middleware(app, gx_app)
|
||||
add_request_id_middleware(app)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "lib")))
|
||||
|
||||
import click
|
||||
import yaml
|
||||
|
||||
from galaxy.webapps.galaxy.fast_app import get_openapi_schema
|
||||
|
||||
|
||||
@click.command("Write openapi schema to path")
|
||||
@click.argument("schema_path", type=click.Path(dir_okay=False, writable=True), required=False)
|
||||
def write_open_api_schema(schema_path):
|
||||
openapi_schema = get_openapi_schema()
|
||||
if schema_path:
|
||||
if schema_path.endswith((".yml", ".yml")):
|
||||
with open(schema_path, "w") as f:
|
||||
yaml.safe_dump(openapi_schema, f)
|
||||
else:
|
||||
with open(schema_path, "w") as f:
|
||||
json.dump(openapi_schema, f, sort_keys=True)
|
||||
else:
|
||||
print(json.dumps(openapi_schema, sort_keys=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
write_open_api_schema()
|
||||
Reference in New Issue
Block a user