Merge pull request #15106 from mvdbeek/typescript_api_client

Add autogenerated typescript API client & use it
This commit is contained in:
Dannon
2022-12-06 09:07:51 -05:00
committed by GitHub
27 changed files with 14345 additions and 168 deletions
+4 -1
View File
@@ -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
+2
View File
@@ -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}` };
});
}
+10 -13
View File
@@ -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;
};
-72
View File
@@ -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);
}
}
}
+78
View File
@@ -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,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
View File
@@ -1 +1 @@
export { getDatatypesMapper } from "./factory.js";
export { getDatatypesMapper } from "./factory";
+4 -14
View File
@@ -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);
-20
View File
@@ -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));
}
}
+34
View File
@@ -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");
+4 -1
View File
@@ -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;
}
+17
View File
@@ -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] });
+2
View File
@@ -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
View File
@@ -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==
+26 -2
View File
@@ -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)
+29
View File
@@ -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()