From 2eaf89f4a8942a19a1c1430e60e86822ad2a0906 Mon Sep 17 00:00:00 2001 From: juliecoust Date: Mon, 27 Sep 2021 16:27:51 +0200 Subject: [PATCH] WIP improving API documentation #24 --- openapi.json | 1100 ++++++++++++++------- py/API_models/crud.py | 224 +++-- py/API_models/exports.py | 12 +- py/API_models/helpers/DataclassToModel.py | 8 +- py/API_models/login.py | 5 +- py/API_models/merge.py | 2 +- py/API_models/subset.py | 14 +- py/main.py | 483 ++++++--- 8 files changed, 1248 insertions(+), 600 deletions(-) diff --git a/openapi.json b/openapi.json index 275e0781..6edccf9a 100644 --- a/openapi.json +++ b/openapi.json @@ -21,7 +21,7 @@ "authentification" ], "summary":"Login", - "description":"Login barrier. If successful, the login will return a JWT which will have to be used\nin Bearer authentication scheme for subsequent calls.\n\n-`username`: User *email* which was used during registration\n\n-`password`: User password", + "description":"**Login barrier,** \n\nIf successful, the login will returns a **JWT** which will have to be used\nin bearer authentication scheme for subsequent calls.", "operationId":"login_login_post", "requestBody":{ "content":{ @@ -38,7 +38,11 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Login Login Post", + "type":"string" + }, + "example":"eyJ1c2VyX2lkIjo5OTN9.YUmHHw.-X4tsLsYbwldKL6vDgO3o4-aAxE" } } }, @@ -61,7 +65,7 @@ "users" ], "summary":"Get Users", - "description":"Return the list of users. For admins only.", + "description":"Returns the list of **all users** with their information. \n\n🔒 *For admins only.*", "operationId":"get_users_users_get", "responses":{ "200":{ @@ -92,7 +96,7 @@ "users" ], "summary":"Show Current User", - "description":"Return currently authenticated user. On top of DB fields, 'can_do' lists the allowed system-wide actions:\n CREATE_PROJECT = 1\n ADMINISTRATE_APP = 2\n ADMINISTRATE_USERS = 3\n CREATE_TAXON = 4", + "description":"Returns **currently authenticated user's** information, permissions and last used projects.", "operationId":"show_current_user_users_me_get", "responses":{ "200":{ @@ -119,7 +123,7 @@ "users" ], "summary":"Get Current User Prefs", - "description":"Return one preference, for project and currently authenticated user.", + "description":"**Returns one preference**, for a project and the currently authenticated user.\n\nAvailable keys are **cwd**, **img_import** and **filters**.", "operationId":"get_current_user_prefs_users_my_preferences__project_id__get", "parameters":[ { @@ -132,11 +136,14 @@ "in":"path" }, { - "required":true, + "description":"The preference key.", + "required":false, "schema":{ "title":"Key", - "type":"string" + "type":"string", + "description":"The preference key." }, + "example":"filters", "name":"key", "in":"query" } @@ -149,7 +156,8 @@ "schema":{ "title":"Response Get Current User Prefs Users My Preferences Project Id Get", "type":"string" - } + }, + "example":"{\"dispfield\": \" dispfield_orig_id dispfield_classif_auto_score dispfield_classif_when\", \"ipp\": \"1000\", \"magenabled\": \"1\", \"popupenabled\": \"1\", \"sortby\": \"orig_id\", \"sortorder\": \"asc\", \"statusfilter\": \"P\", \"zoom\": \"90\"}" } } }, @@ -175,7 +183,7 @@ "users" ], "summary":"Set Current User Prefs", - "description":"Set one preference, for project and currently authenticated user.\n-`key`: The preference key\n-`value`: The value to set this preference to.", + "description":"**Sets one preference**, for a project and for the currently authenticated user.\n\nAvailable keys are **cwd**, **img_import** and **filters**.\n\nThe key disappears if set to empty string.", "operationId":"set_current_user_prefs_users_my_preferences__project_id__put", "parameters":[ { @@ -188,20 +196,26 @@ "in":"path" }, { - "required":true, + "description":"The preference key.", + "required":false, "schema":{ "title":"Key", - "type":"string" + "type":"string", + "description":"The preference key." }, + "example":"filters", "name":"key", "in":"query" }, { - "required":true, + "description":"The value to set this preference to.", + "required":false, "schema":{ "title":"Value", - "type":"string" + "type":"string", + "description":"The value to set this preference to." }, + "example":"{\"dispfield\": \" dispfield_orig_id dispfield_classif_auto_score dispfield_classif_when dispfield_random_value\", \"ipp\": \"500\", \"magenabled\": \"1\", \"popupenabled\": \"1\", \"sortby\": \"orig_id\", \"sortorder\": \"asc\", \"statusfilter\": \"\", \"zoom\": \"90\"}", "name":"value", "in":"query" } @@ -211,7 +225,8 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{}, + "example":{} } } }, @@ -239,15 +254,18 @@ "users" ], "summary":"Search User", - "description":"Search users using various criteria, search is case insensitive and might contain % chars.", + "description":"**Search users using various criteria**, search is case insensitive and might contain % chars.", "operationId":"search_user_users_search_get", "parameters":[ { + "description":"Search by name, use % for searching with 'any char'.", "required":false, "schema":{ - "title":"By Name", - "type":"string" + "title":"search by name", + "type":"string", + "description":"Search by name, use % for searching with 'any char'." }, + "example":"%userNa%", "name":"by_name", "in":"query" } @@ -291,7 +309,7 @@ "users" ], "summary":"Get User", - "description":"Return a single user by its id.", + "description":"Returns **information about the user** corresponding to the given id.", "operationId":"get_user_users__user_id__get", "parameters":[ { @@ -339,7 +357,7 @@ "collections" ], "summary":"Create Collection", - "description":"Create a collection with at least one project inside.\n\n*Currently only for admins*", + "description":"**Create a collection** with at least one project inside.\n\nReturns the created collection Id.\n\n🔒 *For admins only.*", "operationId":"create_collection_collections_create_post", "requestBody":{ "content":{ @@ -356,7 +374,11 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Create Collection Collections Create Post", + "type":"integer" + }, + "example":1 } } }, @@ -384,15 +406,18 @@ "collections" ], "summary":"Search Collections", - "description":"Search for collections.\nUse % for searching with 'any char'.\n\n*Currently only for admins*", + "description":"**Search for collections.**\n\n🔒 *For admins only.*", "operationId":"search_collections_collections_search_get", "parameters":[ { - "required":true, + "description":"Search by title, use % for searching with 'any char'.", + "required":false, "schema":{ "title":"Title", - "type":"string" + "type":"string", + "description":"Search by title, use % for searching with 'any char'." }, + "example":"%coll%", "name":"title", "in":"query" } @@ -436,15 +461,18 @@ "collections" ], "summary":"Collection By Title", - "description":"Return the single collection with this title.\nFor published datasets.\n!!! DO NOT MODIFY BEHAVIOR !!!", + "description":"Return the **single collection with this title**.\n\n*For published datasets.*\n\n⚠️ DO NOT MODIFY BEHAVIOR ⚠️ ", "operationId":"collection_by_title_collections_by_title_get", "parameters":[ { - "required":true, + "description":"Search by **exact** title", + "required":false, "schema":{ - "title":"Q", - "type":"string" + "title":"Title", + "type":"string", + "description":"Search by **exact** title" }, + "example":"My collection", "name":"q", "in":"query" } @@ -479,15 +507,18 @@ "collections" ], "summary":"Collection By Short Title", - "description":"Return the single collection with this title.\nFor published datasets.\n!!! DO NOT MODIFY BEHAVIOR !!!", + "description":"Return the **single collection with this short title**.\n\n*For published datasets.*\n\n⚠️ DO NOT MODIFY BEHAVIOR ⚠️ ", "operationId":"collection_by_short_title_collections_by_short_title_get", "parameters":[ { - "required":true, + "description":"Search by **exact** short title", + "required":false, "schema":{ - "title":"Q", - "type":"string" + "title":"Short title", + "type":"string", + "description":"Search by **exact** short title" }, + "example":"My coll", "name":"q", "in":"query" } @@ -522,7 +553,7 @@ "collections" ], "summary":"Get Collection", - "description":"Read a collection by its ID.\n\n*Currently only for admins*", + "description":"Returns **information about the collection** corresponding to the given id.\n\n 🔒 *For admins only.*", "operationId":"get_collection_collections__collection_id__get", "parameters":[ { @@ -568,7 +599,7 @@ "collections" ], "summary":"Update Collection", - "description":"Update the collection. Note that some updates are silently failing when not compatible\nwith the composing projects.\n\n*Currently only for admins*", + "description":"**Update the collection**. Note that some updates are silently failing when not compatible\n with the composing projects.\n\n 🔒 *For admins only.*", "operationId":"update_collection_collections__collection_id__put", "parameters":[ { @@ -596,7 +627,8 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{}, + "example":{} } } }, @@ -622,7 +654,7 @@ "collections" ], "summary":"Erase Collection", - "description":"Delete the collection, i.e. the precious fields, as the projects are just linked-at from the collection.", + "description":"**Delete the collection**, \n\ni.e. the precious fields, as the projects are just linked-at from the collection.\n\n🔒 *For admins only.*", "operationId":"erase_collection_collections__collection_id__delete", "parameters":[ { @@ -640,7 +672,11 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Erase Collection Collections Collection Id Delete", + "type":"integer" + }, + "example":0 } } }, @@ -668,7 +704,7 @@ "collections" ], "summary":"Emodnet Format Export", - "description":"Export the collection in EMODnet format, @see https://www.emodnet-ingestion.eu/\nProduces a DwC-A archive into a temporary directory, ready for download.\n- param `dry_run`: If set, then only a diagnostic of doability will be done.\n- param `with_zeroes`: If set, then *absent* records will be generated, in the relevant samples,\n for categories present in other samples.\n- param `with_computations`: If set, then an attempt will be made to compute organisms concentrations\nand biovolumes.\n- param `auto_morpho`: If set, then any object classified on a Morpho category will be added to\n the count of the nearest Phylo parent, upward in the tree.\n\nMaybe useful, a reader in Python: https://python-dwca-reader.readthedocs.io/en/latest/index.html\n\n*Currently only for admins*", + "description":"**Export the collection in EMODnet format**, @see https://www.emodnet-ingestion.eu\n\nProduces a DwC-A archive into a temporary directory, ready for download.\n\nMaybe useful, a reader in Python: https://python-dwca-reader.readthedocs.io/en/latest/index.html\n\n🔒 *For admins only.*", "operationId":"emodnet_format_export_collections__collection_id__export_emodnet_get", "parameters":[ { @@ -681,38 +717,50 @@ "in":"path" }, { - "required":true, + "description":"If set, then only a diagnostic of doability will be done.", + "required":false, "schema":{ - "title":"Dry Run", - "type":"boolean" + "title":"Dry run", + "type":"boolean", + "description":"If set, then only a diagnostic of doability will be done." }, + "example":false, "name":"dry_run", "in":"query" }, { - "required":true, + "description":"If set, then *absent* records will be generated, in the relevant samples, for categories present in other samples.", + "required":false, "schema":{ - "title":"With Zeroes", - "type":"boolean" + "title":"With zeroes", + "type":"boolean", + "description":"If set, then *absent* records will be generated, in the relevant samples, for categories present in other samples." }, + "example":false, "name":"with_zeroes", "in":"query" }, { - "required":true, + "description":"If set, then any object classified on a Morpho category will be added to the count of the nearest Phylo parent, upward in the tree.", + "required":false, "schema":{ - "title":"Auto Morpho", - "type":"boolean" + "title":"Auto morpho", + "type":"boolean", + "description":"If set, then any object classified on a Morpho category will be added to the count of the nearest Phylo parent, upward in the tree." }, + "example":false, "name":"auto_morpho", "in":"query" }, { - "required":true, + "description":"If set, then an attempt will be made to compute organisms concentrations and biovolumes.", + "required":false, "schema":{ - "title":"With Computations", - "type":"boolean" + "title":"With computations", + "type":"boolean", + "description":"If set, then an attempt will be made to compute organisms concentrations and biovolumes." }, + "example":false, "name":"with_computations", "in":"query" } @@ -752,67 +800,82 @@ "projects" ], "summary":"Search Projects", - "description":"Return projects which the current user has explicit permission to access, with search options.\n- `param` not_granted: Return projects on which the current user has _no permission_, but visible to him/her\n- `param` for_managing: Return projects that can be written to (including erased) by the current user\n- `param` title_filter: Use this pattern for matching returned projects names\n- `param` instrument_filter: Only return projects where this instrument was used\n- `param` filter_subset: Only return projects having 'subset' in their names\n- `params` order_field, window_start, window_size: See accompanying description.\n\nNote that, for performance reasons, in returned ProjectModels, field 'highest_rank' is NOT valued\n(unlike in simple query). The same information can be found in 'managers', 'annotators' and 'viewers' lists.", + "description":"Returns **projects which the current user has explicit permission to access, with search options.**\n\nNote that, for performance reasons, in returned ProjectModels, field 'highest_rank' is NOT valued\n(unlike in simple query). The same information can be found in 'managers', 'annotators' and 'viewers' lists.", "operationId":"search_projects_projects_search_get", "parameters":[ { "required":false, "deprecated":true, "schema":{ - "title":"Also Others", + "title":"Also others", "type":"boolean", "default":false }, + "example":false, "name":"also_others", "in":"query" }, { + "description":"Return projects on which the current user has _no permission_, but visible to him/her", "required":false, "schema":{ - "title":"Not Granted", + "title":"Not granted", "type":"boolean", + "description":"Return projects on which the current user has _no permission_, but visible to him/her", "default":false }, + "example":false, "name":"not_granted", "in":"query" }, { + "description":"Return projects that can be written to (including erased) by the current user", "required":false, "schema":{ - "title":"For Managing", + "title":"Nor managing", "type":"boolean", + "description":"Return projects that can be written to (including erased) by the current user", "default":false }, + "example":false, "name":"for_managing", "in":"query" }, { + "description":"Use this pattern for matching returned projects names", "required":false, "schema":{ - "title":"Title Filter", + "title":"Title filter", "type":"string", - "default":"" + "description":"Use this pattern for matching returned projects names" }, + "example":"Tara", "name":"title_filter", "in":"query" }, { + "description":"Only return projects where this instrument was used", "required":false, "schema":{ - "title":"Instrument Filter", + "title":"Instrument filter", "type":"string", + "description":"Only return projects where this instrument was used", "default":"" }, + "example":"uvp5", "name":"instrument_filter", "in":"query" }, { + "description":"Only return projects having 'subset' in their names", "required":false, "schema":{ - "title":"Filter Subset", + "title":"Filter subset", "type":"boolean", + "description":"Only return projects having 'subset' in their names", "default":false }, + "example":true, "name":"filter_subset", "in":"query" }, @@ -820,10 +883,11 @@ "description":"One of ['instrument', 'highest_right', 'license', 'projid', 'title', 'visible', 'status', 'objcount', 'pctvalidated', 'pctclassified', 'classifsettings', 'classiffieldlist', 'popoverfieldlist', 'comments', 'projtype', 'rf_models_used', 'cnn_network_id']", "required":false, "schema":{ - "title":"Order Field", + "title":"Order field", "type":"string", "description":"One of ['instrument', 'highest_right', 'license', 'projid', 'title', 'visible', 'status', 'objcount', 'pctvalidated', 'pctclassified', 'classifsettings', 'classiffieldlist', 'popoverfieldlist', 'comments', 'projtype', 'rf_models_used', 'cnn_network_id']" }, + "example":"instrument", "name":"order_field", "in":"query" }, @@ -831,10 +895,11 @@ "description":"Skip `window_start` before returning data", "required":false, "schema":{ - "title":"Window Start", + "title":"Window start", "type":"integer", "description":"Skip `window_start` before returning data" }, + "example":"0", "name":"window_start", "in":"query" }, @@ -842,10 +907,11 @@ "description":"Return only `window_size` lines", "required":false, "schema":{ - "title":"Window Size", + "title":"Window size", "type":"integer", "description":"Return only `window_size` lines" }, + "example":"100", "name":"window_size", "in":"query" } @@ -889,7 +955,7 @@ "projects" ], "summary":"Create Project", - "description":"Create an empty project with only a title, and return its number.\nThe project will be managed by current user.\nThe user has to be app administrator or project creator.", + "description":"**Create an empty project with only a title,** and **return its id**.\n\nThe project will be managed by current user.\n\n🔒 The user has to be *app administrator* or *project creator*.", "operationId":"create_project_projects_create_post", "requestBody":{ "content":{ @@ -906,7 +972,18 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Create Project Projects Create Post", + "anyOf":[ + { + "type":"integer" + }, + { + "type":"string" + } + ] + }, + "example":44 } } }, @@ -934,7 +1011,7 @@ "projects" ], "summary":"Project Subset", - "description":"Subset a project into another one.", + "description":"**Subset a project into another one.**", "operationId":"project_subset_projects__project_id__subset_post", "parameters":[ { @@ -992,7 +1069,7 @@ "projects" ], "summary":"Project Query", - "description":"Read project if it exists for current user, eventually for managing it.", + "description":"**Returns project** if it exists for current user, eventually for managing it.", "operationId":"project_query_projects__project_id__get", "parameters":[ { @@ -1005,12 +1082,14 @@ "in":"path" }, { + "description":"For managing this project.", "required":false, "schema":{ - "title":"For Managing", + "title":"For managinig", "type":"boolean", - "default":false + "description":"For managing this project." }, + "example":false, "name":"for_managing", "in":"query" } @@ -1048,7 +1127,7 @@ "projects" ], "summary":"Update Project", - "description":"Update the project.\nNote that some fields will NOT be updated and simply ignored, e.g. *free_cols.", + "description":"**Update the project**.\n\nNote that some fields will **NOT** be updated and simply ignored, e.g. *free_cols*.", "operationId":"update_project_projects__project_id__put", "parameters":[ { @@ -1076,7 +1155,8 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{}, + "example":{} } } }, @@ -1102,7 +1182,7 @@ "projects" ], "summary":"Erase Project", - "description":"Delete the project.\n Optionally, if \"only_objects\" is set, the project structure is kept,\n but emptied from any object/sample/acquisition/process\n Otherwise, no trace of the project will remain in the database.", + "description":"**Delete the project.**\n \nOptionally, if \"only_objects\" is set, the project structure is kept,\nbut emptied from any object, sample, acquisition and process.\n\nOtherwise, no trace of the project will remain in the database.\n\n**Returns** the number of : **deleted objects**, 0, **deleated image rows** and **deleated image files**.", "operationId":"erase_project_projects__project_id__delete", "parameters":[ { @@ -1115,12 +1195,15 @@ "in":"path" }, { + "description":"If set, the project structure is kept, but emptied from any object, sample, acquisition and process.", "required":false, "schema":{ - "title":"Only Objects", + "title":"Only objects", "type":"boolean", + "description":"If set, the project structure is kept, but emptied from any object, sample, acquisition and process.", "default":false }, + "example":false, "name":"only_objects", "in":"query" } @@ -1130,7 +1213,13 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{}, + "example":[ + 100, + 0, + 10, + 10 + ] } } }, @@ -1158,25 +1247,31 @@ "projects" ], "summary":"Project Set Get Stats", - "description":"Read projects statistics, i.e. used taxa and classification states.\n\nIf several `ìds` are provided, one stat record will be returned per project.\nIf several `taxa_ids` are provided, one stat record will be returned per requested taxa, if populated.\nIf `taxa_ids` is 'all', all valued taxa in the project(s) are returned.", + "description":"**Returns projects statistics**, i.e. used taxa and classification states.", "operationId":"project_set_get_stats_project_set_taxo_stats_get", "parameters":[ { - "required":true, + "description":"String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project.", + "required":false, "schema":{ "title":"Ids", - "type":"string" + "type":"string", + "description":"String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project." }, + "example":"1", "name":"ids", "in":"query" }, { + "description":"**If several taxa_ids are provided**, one stat record will be returned per requested taxa, if populated.\n \n **If taxa_ids is all**, all valued taxa in the project(s) are returned.", "required":false, "schema":{ "title":"Taxa Ids", "type":"string", + "description":"**If several taxa_ids are provided**, one stat record will be returned per requested taxa, if populated.\n \n **If taxa_ids is all**, all valued taxa in the project(s) are returned.", "default":"" }, + "example":"all", "name":"taxa_ids", "in":"query" } @@ -1220,15 +1315,18 @@ "projects" ], "summary":"Project Set Get User Stats", - "description":"Read projects user statistics, i.e. a summary of the work done by users in the\nrequired projects. The returned values are a detail _per project_, so size of input list\nequals size of output list.", + "description":"**Returns projects user statistics**, i.e. a summary of the work done by users in the\nrequired projects. \n\nThe returned values are a detail per project, so size of input list equals size of output list.", "operationId":"project_set_get_user_stats_project_set_user_stats_get", "parameters":[ { - "required":true, + "description":"String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project.", + "required":false, "schema":{ "title":"Ids", - "type":"string" + "type":"string", + "description":"String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project." }, + "example":"1", "name":"ids", "in":"query" } @@ -1244,7 +1342,25 @@ "items":{ "$ref":"#/components/schemas/ProjectUserStatsModel" } - } + }, + "example":[ + { + "projid":1, + "annotators":[ + { + "id":1267, + "name":"User Name" + } + ], + "activities":[ + { + "id":1267, + "nb_actions":605, + "last_annot":"2021-09-27T13:08:54" + } + ] + } + ] } } }, @@ -1272,7 +1388,7 @@ "projects" ], "summary":"Project Merge", - "description":"Merge another project into this one. It's more a phagocytosis than a merge, as the source will see\nall its objects gone and will be erased.\n- param `dry_run`: If set, then only a diagnostic of doability will be done.", + "description":"**Merge another project into this one.**\n\nIt's more a phagocytosis than a merge, as the source will see\nall its objects gone and will be erased.", "operationId":"project_merge_projects__project_id__merge_post", "parameters":[ { @@ -1285,20 +1401,26 @@ "in":"path" }, { - "required":true, + "description":"Id of the other project. This source project will see all its objects gone and will be erased.", + "required":false, "schema":{ - "title":"Source Project Id", - "type":"integer" + "title":"Source project Id", + "type":"integer", + "description":"Id of the other project. This source project will see all its objects gone and will be erased." }, + "example":2, "name":"source_project_id", "in":"query" }, { - "required":true, + "description":"If set, then only a diagnostic of doability will be done.", + "required":false, "schema":{ - "title":"Dry Run", - "type":"boolean" + "title":"Dry run", + "type":"boolean", + "description":"If set, then only a diagnostic of doability will be done." }, + "example":true, "name":"dry_run", "in":"query" } @@ -1338,7 +1460,7 @@ "projects" ], "summary":"Project Check", - "description":"Check consistency of a project.", + "description":"**Check consistency of a project**.\n\nWith time and bugs, some consistency problems could be introduced in projects.\nThis service aims at listing them.", "operationId":"project_check_projects__project_id__check_get", "parameters":[ { @@ -1356,7 +1478,14 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Project Check Projects Project Id Check Get", + "type":"array", + "items":{ + "type":"string" + } + }, + "example":[] } } }, @@ -1430,7 +1559,7 @@ "projects" ], "summary":"Project Recompute Geography", - "description":"Recompute geography information for all samples in project.", + "description":"**Recompute geography information** for all samples in project.\n\n🔒 The user has to be *project manager*.", "operationId":"project_recompute_geography_projects__project_id__recompute_geo_post", "parameters":[ { @@ -1448,7 +1577,8 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{}, + "example":{} } } }, @@ -1601,24 +1731,30 @@ "samples" ], "summary":"Samples Search", - "description":"Read samples for a set of projects.\n\n- project_ids: any(non number)-separated list of project numbers\n- id_pattern: sample id textual pattern. Use * or '' for 'any matches'. Match is case-insensitive.", + "description":"**Search for samples**", "operationId":"samples_search_samples_search_get", "parameters":[ { - "required":true, + "description":"String containing the list of one or more project id separated by non-num char.", + "required":false, "schema":{ "title":"Project Ids", - "type":"string" + "type":"string", + "description":"String containing the list of one or more project id separated by non-num char." }, + "example":"1,55", "name":"project_ids", "in":"query" }, { - "required":true, + "description":"Sample id textual pattern. Use * or '' for 'any matches'. Match is case-insensitive.", + "required":false, "schema":{ - "title":"Id Pattern", - "type":"string" + "title":"Pattern Id", + "type":"string", + "description":"Sample id textual pattern. Use * or '' for 'any matches'. Match is case-insensitive." }, + "example":"*", "name":"id_pattern", "in":"query" } @@ -1662,15 +1798,18 @@ "samples" ], "summary":"Sample Set Get Stats", - "description":"Read classification statistics for a set of samples.\nEXPECT A SLOW RESPONSE. No cache of such information anywhere.\n\n- sample_ids: any(non number)-separated list of sample numbers", + "description":"Returns **classification statistics** for the given set of samples.\n\nEXPECT A SLOW RESPONSE : No cache of such information anywhere.", "operationId":"sample_set_get_stats_sample_set_taxo_stats_get", "parameters":[ { - "required":true, + "description":"String containing the list of one or more sample ids separated by non-num char.", + "required":false, "schema":{ "title":"Sample Ids", - "type":"string" + "type":"string", + "description":"String containing the list of one or more sample ids separated by non-num char." }, + "example":"15,5", "name":"sample_ids", "in":"query" } @@ -1686,6 +1825,21 @@ "items":{ "$ref":"#/components/schemas/SampleTaxoStatsModel" } + }, + "example":{ + "nb_dubious":56, + "nb_predicted":5500, + "nb_unclassified":0, + "nb_validated":1345, + "projid":1, + "used_taxa":[ + 45072, + 78418, + 84963, + 85011, + 85012, + 85078 + ] } } } @@ -1714,7 +1868,7 @@ "samples" ], "summary":"Update Samples", - "description":"Do the required update for each sample in the set. Any non-null field in the model is written to\nevery impacted sample.\n Return the number of updated entities.", + "description":"Do the required **update for each sample in the set.** \n\nAny non-null field in the model is written to every impacted sample.\n\n**Returns the number of updated entities.**", "operationId":"update_samples_sample_set_update_post", "requestBody":{ "content":{ @@ -1731,7 +1885,11 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Update Samples Sample Set Update Post", + "type":"integer" + }, + "example":1 } } }, @@ -1759,7 +1917,7 @@ "samples" ], "summary":"Sample Query", - "description":"Read a single object.", + "description":"Returns **information about the sample** corresponding to the given id.", "operationId":"sample_query_sample__sample_id__get", "parameters":[ { @@ -1807,15 +1965,18 @@ "acquisitions" ], "summary":"Acquisitions Search", - "description":"Read all acquisitions for a project.", + "description":"Returns the **list of all acquisitions for a given project**.", "operationId":"acquisitions_search_acquisitions_search_get", "parameters":[ { - "required":true, + "description":"The project id", + "required":false, "schema":{ - "title":"Project Id", - "type":"integer" + "title":"Project id", + "type":"integer", + "description":"The project id" }, + "example":1, "name":"project_id", "in":"query" } @@ -1859,7 +2020,7 @@ "acquisitions" ], "summary":"Update Acquisitions", - "description":"Do the required update for each acquisition in the set.\n Return the number of updated entities.", + "description":"Do the required **update for each acquisition in the set**.\n\nReturn the number of updated entities.", "operationId":"update_acquisitions_acquisition_set_update_post", "requestBody":{ "content":{ @@ -1876,7 +2037,11 @@ "description":"Successful Response", "content":{ "application/json":{ - "schema":{} + "schema":{ + "title":"Response Update Acquisitions Acquisition Set Update Post", + "type":"integer" + }, + "example":2 } } }, @@ -1904,7 +2069,7 @@ "acquisitions" ], "summary":"Acquisition Query", - "description":"Read a single object.", + "description":"Returns **information about the acquisition** corresponding to the given id.", "operationId":"acquisition_query_acquisition__acquisition_id__get", "parameters":[ { @@ -1952,15 +2117,18 @@ "instrument" ], "summary":"Instrument Query", - "description":"Query for instruments, inside specific project(s).", + "description":"Returns the list of instruments, inside specific project(s).", "operationId":"instrument_query_instruments__get", "parameters":[ { - "required":true, + "description":"String containing the list of one or more project id separated by non-num char.", + "required":false, "schema":{ - "title":"Project Ids", - "type":"string" + "title":"Projects ids", + "type":"string", + "description":"String containing the list of one or more project id separated by non-num char." }, + "example":"1,2,3", "name":"project_ids", "in":"query" } @@ -1976,7 +2144,11 @@ "items":{ "type":"string" } - } + }, + "example":[ + "uvp5", + "zooscan" + ] } } }, @@ -3988,32 +4160,41 @@ "schemas":{ "AcquisitionModel":{ "title":"AcquisitionModel", - "required":[ - "acq_sample_id", - "orig_id" - ], "type":"object", "properties":{ "acquisid":{ - "title":"Acquisid", - "type":"integer" + "title":"Acquisition Id", + "type":"integer", + "description":"The acquisition Id", + "example":144 }, "acq_sample_id":{ - "title":"Acq Sample Id", - "type":"integer" + "title":"Acquisition sample Id", + "type":"integer", + "description":"The acquisition sample Id", + "example":1039 }, "orig_id":{ - "title":"Orig Id", - "type":"string" + "title":"Origine Id", + "type":"string", + "description":"The origine Id", + "example":"uvp5_station1_cast1b" }, "instrument":{ "title":"Instrument", - "type":"string" + "type":"string", + "description":"Instrument used", + "example":"uvp5" }, "free_columns":{ - "title":"Free columns from acquisition mapping in project", + "title":"Free columns", "type":"object", - "default":{} + "description":"Free columns from acquisition mapping in project", + "default":{}, + "example":{ + "bottomdepth":322, + "ship":"suroit" + } } } }, @@ -4064,16 +4245,29 @@ "type":"object", "properties":{ "target_ids":{ - "title":"The IDs of the target entities", + "title":"Target Id", "type":"array", "items":{ "type":"integer" - } + }, + "description":"The IDs of the target entities", + "example":[ + 1, + 5, + 290 + ] }, "updates":{ - "title":"The updates, to do on all impacted entities", + "title":"Updates", "type":"array", - "items":{} + "items":{}, + "description":"The list of updates, to do on all impacted entities. \n\n { \n\n ucol : A column name, pseudo-columns AKA free ones, are OK. \n\n uval : The new value to set, always as a string \n\n }", + "example":[ + { + "ucol":"sub_part", + "uval":"2" + } + ] } } }, @@ -4164,104 +4358,129 @@ "CollectionModel":{ "title":"CollectionModel", "required":[ - "project_ids", - "external_id", - "external_id_system", - "title" + "project_ids" ], "type":"object", "properties":{ "project_ids":{ - "title":"The composing project IDs", + "title":"Project ids", "minItems":1, "type":"array", "items":{ "type":"integer" - } + }, + "description":"The list of composing project IDs", + "example":[ + 1 + ] }, "provider_user":{ - "title":"Is the person who is responsible for the content of this metadata record. \nWriter of the title and abstract.", + "title":"Provider user", "allOf":[ { "$ref":"#/components/schemas/UserModel" } - ] + ], + "description":"Is the person who \n is responsible for the content of this metadata record. Writer of the title and abstract." }, "contact_user":{ - "title":"Is the person who should be contacted in cases of questions regarding the\ncontent of the dataset or any data restrictions. This is also the person who is most likely to\nstay involved in the dataset the longest.", + "title":"Contact user", "allOf":[ { "$ref":"#/components/schemas/UserModel" } - ] + ], + "description":"Is the person who \n should be contacted in cases of questions regarding the content of the dataset or any data restrictions. \n This is also the person who is most likely to stay involved in the dataset the longest." }, "creator_users":{ - "title":"All people who are responsible for the creation of\nthe collection. Data creators should receive credit for their work and should therefore be\nincluded in the citation.", + "title":"Creator users", "type":"array", "items":{ "$ref":"#/components/schemas/UserModel" }, + "description":"All people who \n are responsible for the creation of the collection. Data creators should receive credit \n for their work and should therefore be included in the citation.", "default":[] }, "creator_organisations":{ - "title":"All organisations who are responsible for the creation of\n the collection. Data creators should receive credit for their work and should therefore be\n included in the citation.", + "title":"Creator organisations", "type":"array", "items":{ "type":"string" }, + "description":"All \n organisations who are responsible for the creation of the collection. Data creators should \n receive credit for their work and should therefore be included in the citation.", "default":[] }, "associate_users":{ - "title":"Other person(s) associated with the collection", + "title":"Associate users", "type":"array", "items":{ "$ref":"#/components/schemas/UserModel" }, + "description":"Other person(s) \n associated with the collection", "default":[] }, "associate_organisations":{ - "title":"Other organisation(s) associated with the collection", + "title":"Associate organisations", "type":"array", "items":{ "type":"string" }, + "description":"Other \n organisation(s) associated with the collection", "default":[] }, "id":{ "title":"Id", - "type":"integer" + "type":"integer", + "description":"The collection Id", + "example":1 }, "external_id":{ "title":"External Id", - "type":"string" + "type":"string", + "description":"The external Id", + "example":"" }, "external_id_system":{ - "title":"External Id System", - "type":"string" + "title":"External id system", + "type":"string", + "description":"The external Id system", + "example":"" }, "title":{ "title":"Title", - "type":"string" + "type":"string", + "description":"The collection title", + "example":"My collection" }, "short_title":{ - "title":"Short Title", - "type":"string" + "title":"Short title", + "type":"string", + "description":"The collection short title", + "example":"My coll" }, "citation":{ "title":"Citation", - "type":"string" + "type":"string", + "description":"The collection citation", + "example":"" }, "license":{ "title":"License", - "type":"string" + "type":"string", + "description":"The collection license", + "example":"CC BY 4.0" }, "abstract":{ "title":"Abstract", - "type":"string" + "type":"string", + "description":"The collection abstract", + "example":"" }, "description":{ "title":"Description", - "type":"string" + "type":"string", + "description":"The collection description", + "example":"" } }, "description":"Collection + computed" @@ -4309,16 +4528,22 @@ "type":"object", "properties":{ "title":{ - "title":"The collection title", - "type":"string" + "title":"Title", + "type":"string", + "description":"The collection title", + "example":"My collection" }, "project_ids":{ - "title":"The composing project IDs", + "title":"Project ids", "minItems":1, "type":"array", "items":{ "type":"integer" - } + }, + "description":"The list of composing project IDs", + "example":[ + 1 + ] } } }, @@ -4330,17 +4555,23 @@ "type":"object", "properties":{ "clone_of_id":{ - "title":"If set, clone specified Project", - "type":"integer" + "title":"Clone of id", + "type":"integer", + "description":"If set, clone specified Project", + "example":2 }, "title":{ - "title":"The project title", - "type":"string" + "title":"Title", + "type":"string", + "description":"The project title", + "example":"My new project title" }, "visible":{ - "title":"The project is created visible", + "title":"Visible", "type":"boolean", - "default":true + "description":"If True, the project is created visible", + "default":true, + "example":true } } }, @@ -4400,25 +4631,31 @@ "type":"object", "properties":{ "errors":{ - "title":"Showstopper problems found while building the archive.", + "title":"Errors", "type":"array", "items":{ "type":"string" }, - "default":[] + "description":"Showstopper problems found while building the archive.", + "default":[], + "example":[] }, "warnings":{ - "title":"Problems found while building the archive, which do not prevent producing it.", + "title":"Warnings", "type":"array", "items":{ "type":"string" }, - "default":[] + "description":"Problems found while building the archive, which do not prevent producing it.", + "default":[], + "example":[] }, "job_id":{ - "title":"The created job, 0 if there were problems.", + "title":"Job Id", "type":"integer", - "default":0 + "description":"The created job, 0 if there were problems.", + "default":0, + "example":1 } }, "description":"EMODNet format export response." @@ -4501,25 +4738,31 @@ "type":"object", "properties":{ "errors":{ - "title":"Showstopper problems found while building the archive.", + "title":"Errors", "type":"array", "items":{ "type":"string" }, - "default":[] + "description":"Showstopper problems found while building the archive.", + "default":[], + "example":[] }, "warnings":{ - "title":"Problems found while building the archive, which do not prevent producing it.", + "title":"Warnings", "type":"array", "items":{ "type":"string" }, - "default":[] + "description":"Problems found while building the archive, which do not prevent producing it.", + "default":[], + "example":[] }, "job_id":{ - "title":"The created job, 0 if there were problems.", + "title":"Job Id", "type":"integer", - "default":0 + "description":"The created job, 0 if there were problems.", + "default":0, + "example":1 } }, "description":"Export response.\nTODO: Should inherit the other way round." @@ -4845,15 +5088,17 @@ "title":"LoginReq", "type":"object", "properties":{ - "username":{ - "title":"Username", + "password":{ + "title":"User's password", "type":"string", - "default":"User email, like in Web UI" + "description":"User password", + "example":"UserPassword!" }, - "password":{ - "title":"Password", + "username":{ + "title":"User's eamil", "type":"string", - "default":"User password" + "description":"User email used during registration", + "example":"user@email.com" } } }, @@ -4862,11 +5107,12 @@ "type":"object", "properties":{ "errors":{ - "title":"The errors found during processing.", + "title":"Errors", "type":"array", "items":{ "type":"string" }, + "description":"The errors found during processing.", "default":[] } }, @@ -5348,184 +5594,237 @@ "ProjectModel":{ "title":"ProjectModel", "required":[ - "title" + "projid", + "title", + "visible", + "status", + "objcount", + "pctvalidated", + "pctclassified", + "classifsettings", + "classiffieldlist", + "popoverfieldlist", + "comments", + "projtype", + "rf_models_used", + "cnn_network_id" ], "type":"object", "properties":{ "obj_free_cols":{ - "title":"Object free columns", + "title":"Object free cols", "type":"object", "additionalProperties":{ "type":"string" }, - "default":{} + "description":"Object free columns", + "default":{}, + "example":{ + "area":"n01", + "esd":"n02" + } }, "sample_free_cols":{ - "title":"Sample free columns", + "title":"Sample free cols", "type":"object", "additionalProperties":{ "type":"string" }, - "default":{} + "description":"Sample free columns", + "default":{}, + "example":{ + "barcode":"t01" + } }, "acquisition_free_cols":{ - "title":"Acquisition free columns", + "title":"Acquisition free cols", "type":"object", "additionalProperties":{ "type":"string" }, - "default":{} + "description":"Acquisition free columns", + "default":{}, + "example":{ + "flash_delay":"t01" + } }, "process_free_cols":{ - "title":"Process free columns", + "title":"Process free cols", "type":"object", "additionalProperties":{ "type":"string" }, - "default":{} + "description":"Process free columns", + "default":{}, + "example":{ + "nb_images":"t01" + } }, "init_classif_list":{ - "title":"Favorite taxa used in classification", + "title":"Init classification list", "type":"array", "items":{ "type":"integer" }, - "default":[] + "description":"Favorite taxa used in classification", + "default":[], + "example":[ + 5, + 11493, + 11498, + 11509 + ] }, "managers":{ - "title":"Managers of this project", + "title":"Managers", "type":"array", "items":{ "$ref":"#/components/schemas/UserModel" }, + "description":"Managers of this project", "default":[] }, "annotators":{ - "title":"Annotators of this project, if not manager", + "title":"Annotators", "type":"array", "items":{ "$ref":"#/components/schemas/UserModel" }, + "description":"Annotators of this project, if not manager", "default":[] }, "viewers":{ - "title":"Viewers of this project, if not manager nor annotator", + "title":"Viewers", "type":"array", "items":{ "$ref":"#/components/schemas/UserModel" }, + "description":"Viewers of this project, if not manager nor annotator", "default":[] }, "instrument":{ - "title":"This project's instrument. Transitory: if several of them, then coma-separated", - "type":"string" + "title":"Instrument", + "type":"string", + "description":"This project's instrument. Transitory: if several of them, then coma-separated", + "example":"zooscan" }, "contact":{ - "title":"The contact person is a manager who serves as the contact person for other users and EcoTaxa's managers.", + "title":"Contact", "allOf":[ { "$ref":"#/components/schemas/UserModel" } - ] + ], + "description":"The contact person is a manager who serves as the contact person for other users and EcoTaxa's managers." }, "highest_right":{ - "title":"The highest right for requester on this project. One of 'Manage', 'Annotate', 'View'.", + "title":"Highest right", "type":"string", - "default":"" + "description":"The highest right for requester on this project. One of 'Manage', 'Annotate', 'View'.", + "default":"", + "example":"View" }, "license":{ "title":"License", "type":"string" }, "projid":{ - "title":"Projid", - "type":"integer" + "title":"Project Id", + "type":"integer", + "description":"The project Id", + "example":4824 }, "title":{ "title":"Title", - "type":"string" + "type":"string", + "description":"The project title", + "example":"MyProject" }, "visible":{ "title":"Visible", - "type":"boolean" + "type":"boolean", + "description":"The project visibility", + "example":false }, "status":{ "title":"Status", - "type":"string" + "type":"string", + "description":"The project status", + "example":"Annotate" }, "objcount":{ - "title":"Objcount", - "type":"number" + "title":"Object count", + "type":"number", + "description":"The number of objects", + "example":32292.0 }, "pctvalidated":{ - "title":"Pctvalidated", - "type":"number" + "title":"Percentage validated", + "type":"number", + "description":"Percentage of validated images.", + "example":0.015483711135885049 }, "pctclassified":{ - "title":"Pctclassified", - "type":"number" + "title":"Percentage classified", + "type":"number", + "description":"Percentage of classified images.", + "example":100.0 }, "classifsettings":{ - "title":"Classifsettings", - "type":"string" + "title":"Classification settings", + "type":"string", + "example":"baseproject=1602\ncritvar=%area,angle,area,area_exc,bx,by,cdexc,centroids,circ.,circex,convarea,convperim,cv,elongation,esd,fcons,feret,feretareaexc,fractal,height,histcum1,histcum2,histcum3,intden,kurt,lat_end,lon_end,major,max,mean,meanpos,median,min,minor,mode,nb1,nb2,perim.,perimareaexc,perimferet,perimmajor,range,skelarea,skew,slope,sr,stddev,symetrieh,symetriehc,symetriev,symetrievc,thickr,width,x,xm,xstart,y,ym,ystart\nposttaxomapping=\nseltaxo=45074,84963,61990,13333,82399,61973,62005,25930,25932,61996,78426,81941,11514,85076,85061,30815,85185,92230,85079,84993,25824,85115,85004,26525,25944,11509,26524,92112,84976,25942,84980,85078,78418,84977,85060,61993,61991,85069,81871,74144,11758,72431,13381,11518,5,18758,85117,92042,84968,84997,87826,92236,92237,92039,84989,85193,83281,78412,92239,71617,81977,45071,12865,85044,81940,85067,12908,85116,56693,85008,92139,92068\nusemodel_foldername=testln1" }, "classiffieldlist":{ - "title":"Classiffieldlist", - "type":"string" + "title":"Classification field list", + "type":"string", + "example":"depth_min=depth_min\r\ndepth_max=depth_max\r\narea=area [pixel]\r\nmean=mean [0-255]\r\nfractal=fractal\r\nmajor=major [pixel]\r\nsymetrieh=symetrieh\r\ncirc.=circ\r\nferet = Feret [pixel]" }, "popoverfieldlist":{ - "title":"Popoverfieldlist", - "type":"string" + "title":"Pop over field list", + "type":"string", + "example":"depth_min=depth_min\r\ndepth_max=depth_max\r\narea=area [pixel]\r\nmean=mean [0-255]\r\nfractal=fractal\r\nmajor=major [pixel]\r\nsymetrieh=symetrieh\r\ncirc.=circ\r\nferet = Feret [pixel]" }, "comments":{ "title":"Comments", - "type":"string" + "type":"string", + "description":"The project comments", + "example":"" }, "projtype":{ - "title":"Projtype", - "type":"string" + "title":"Project type", + "type":"string", + "description":"The type of the project", + "example":"" }, "rf_models_used":{ - "title":"Rf Models Used", - "type":"string" + "title":"Rf models used", + "type":"string", + "example":"" }, "cnn_network_id":{ - "title":"Cnn Network Id", - "type":"string" + "title":"Cnn network id", + "type":"string", + "example":"SCN_zooscan_group1" } }, - "description":"Project + computed", - "example":{ - "obj_free_cols":{ - "area":"n01", - "esd":"n02" - }, - "sample_free_cols":{ - "barcode":"t01" - }, - "acquisition_free_cols":{ - "flash_delay":"t01" - }, - "process_free_cols":{ - "nb_images":"t01" - } - } + "description":"Project + computed" }, "ProjectSummaryModel":{ "title":"ProjectSummaryModel", - "required":[ - "projid", - "title" - ], "type":"object", "properties":{ "projid":{ - "title":"Project unique identifier", - "type":"integer" + "title":"Project Id", + "type":"integer", + "description":"Project unique identifier", + "example":1 }, "title":{ "title":"Project title", - "type":"string" + "type":"string", + "description":"Project's title", + "example":"Zooscan Tara Med" } } }, @@ -5541,32 +5840,51 @@ "type":"object", "properties":{ "projid":{ - "title":"The project id", - "type":"integer" + "title":"projid", + "type":"integer", + "description":"The project id", + "example":1 }, "used_taxa":{ - "title":"The taxa/category ids used inside the project. An id of -1 means some unclassified objects", + "title":"used_taxa", "type":"array", "items":{ "type":"integer" }, - "default":[] + "description":"The taxa/category ids used inside the project. An id of -1 means some unclassified objects", + "default":[], + "example":[ + 45072, + 78418, + 84963, + 85011, + 85012, + 85078 + ] }, "nb_unclassified":{ - "title":"The number of unclassified objects inside the project", - "type":"integer" + "title":"nb_unclassified", + "type":"integer", + "description":"The number of unclassified objects inside the project", + "example":0 }, "nb_validated":{ - "title":"The number of validated objects inside the project", - "type":"integer" + "title":"nb_validated", + "type":"integer", + "description":"The number of validated objects inside the project", + "example":5000 }, "nb_dubious":{ - "title":"The number of dubious objects inside the project", - "type":"integer" + "title":"nb_dubious", + "type":"integer", + "description":"The number of dubious objects inside the project", + "example":56 }, "nb_predicted":{ - "title":"The number of predicted objects inside the project", - "type":"integer" + "title":"nb_predicted", + "type":"integer", + "description":"The number of predicted objects inside the project", + "example":1345 } } }, @@ -5575,60 +5893,83 @@ "type":"object", "properties":{ "projid":{ - "title":"The project id", - "type":"integer" + "title":"Project id", + "type":"integer", + "description":"The project id" }, "annotators":{ - "title":"The users who ever decided on classification or state of objects", + "title":"Annotators", "type":"array", "items":{ "$ref":"#/components/schemas/MinimalUserBO" - } + }, + "description":"The users who ever decided on classification or state of objects" }, "activities":{ - "title":"More details on annotators' activities", + "title":"Activities", "type":"array", "items":{ "$ref":"#/components/schemas/UserActivity" - } + }, + "description":"More details on annotators' activities" } } }, "SampleModel":{ "title":"SampleModel", "required":[ - "orig_id" + "sampleid", + "projid", + "orig_id", + "latitude", + "longitude", + "dataportal_descriptor" ], "type":"object", "properties":{ "sampleid":{ - "title":"Sampleid", - "type":"integer" + "title":"Sample Id", + "type":"integer", + "description":"The sample Id", + "example":100 }, "projid":{ - "title":"Projid", - "type":"integer" + "title":"Project Id", + "type":"integer", + "description":"The project Id", + "example":4 }, "orig_id":{ - "title":"Orig Id", - "type":"string" + "title":"Origine Id", + "type":"string", + "description":"The origine Id", + "example":"dewex_leg2_19" }, "latitude":{ "title":"Latitude", - "type":"number" + "type":"number", + "description":"The latitude", + "example":42.0231666666667 }, "longitude":{ "title":"Longitude", - "type":"number" + "type":"number", + "description":"The longitude", + "example":4.71766666666667 }, "dataportal_descriptor":{ - "title":"Dataportal Descriptor", - "type":"string" + "title":"Dataportal descriptor", + "type":"string", + "example":"" }, "free_columns":{ - "title":"Free columns from sample mapping in project", + "title":"Free columns", "type":"object", - "default":{} + "description":"Free columns from sample mapping in project", + "default":{}, + "example":{ + "flash_delay":"t01" + } } } }, @@ -5637,31 +5978,37 @@ "type":"object", "properties":{ "sample_id":{ - "title":"The sample id", - "type":"integer" + "title":"Sample id", + "type":"integer", + "description":"The sample id" }, "used_taxa":{ - "title":"The taxa/category ids used inside the sample. -1 for unclassified objects", + "title":"Used taxa", "type":"array", "items":{ "type":"integer" - } + }, + "description":"The taxa/category ids used inside the sample. -1 for unclassified objects" }, "nb_unclassified":{ - "title":"The number of unclassified objects inside the sample", - "type":"integer" + "title":"Number unclassified", + "type":"integer", + "description":"The number of unclassified objects inside the sample" }, "nb_validated":{ - "title":"The number of validated objects inside the sample", - "type":"integer" + "title":"Number validated", + "type":"integer", + "description":"The number of validated objects inside the sample" }, "nb_dubious":{ - "title":"The number of dubious objects inside the sample", - "type":"integer" + "title":"Number dubious", + "type":"integer", + "description":"The number of dubious objects inside the sample" }, "nb_predicted":{ - "title":"The number of predicted objects inside the sample", - "type":"integer" + "title":"Number predicted", + "type":"integer", + "description":"The number of predicted objects inside the sample" } } }, @@ -5740,40 +6087,55 @@ "type":"object", "properties":{ "filters":{ - "title":"The filters to apply to project", + "title":"Filters", "type":"object", "additionalProperties":{ "type":"string" }, - "default":{} + "description":"The filters to apply to project", + "default":{}, + "example":{ + "freenum":"n01", + "freenumst":"0" + } }, "dest_prj_id":{ - "title":"The destination project ID.", - "type":"integer" + "title":"Destination project id", + "type":"integer", + "description":"The destination project ID.", + "example":22 }, "group_type":{ - "title":"Define the groups in which to apply limits. C for categories, S for samples, A for acquisitions.", + "title":"Group type", "allOf":[ { "$ref":"#/components/schemas/GroupDefinitions" } - ] + ], + "description":"Define the groups in which to apply limits. C for categories, S for samples, A for acquisitions.", + "example":"A" }, "limit_type":{ - "title":"The type of limit_value: P for %, V for constant, both per group.", + "title":"Limit type", "allOf":[ { "$ref":"#/components/schemas/LimitMethods" } - ] + ], + "description":"The type of limit_value: P for %, V for constant, both per group.", + "example":"P" }, "limit_value":{ - "title":"Limit value, e.g. 20% or 5 per copepoda or 5% per sample.", - "type":"number" + "title":"Limit value", + "type":"number", + "description":"Limit value, e.g. 20% or 5 per copepoda or 5% per sample.", + "example":10.0 }, "do_images":{ - "title":"If set, also clone images.", - "type":"boolean" + "title":"Do images", + "type":"boolean", + "description":"If set, also clone images.", + "example":true } }, "description":"Subset request. " @@ -5786,8 +6148,10 @@ "type":"object", "properties":{ "job_id":{ - "title":"The job created for this operation.", - "type":"integer" + "title":"Job Id", + "type":"integer", + "description":"The job created for this operation.", + "example":143 } }, "description":"Subset response. " @@ -5888,19 +6252,21 @@ "TaxonUsageModel":{ "title":"TaxonUsageModel", "required":[ - "projid", - "title", "nb_validated" ], "type":"object", "properties":{ "projid":{ - "title":"Project unique identifier", - "type":"integer" + "title":"Project Id", + "type":"integer", + "description":"Project unique identifier", + "example":1 }, "title":{ "title":"Project title", - "type":"string" + "type":"string", + "description":"Project's title", + "example":"Zooscan Tara Med" }, "nb_validated":{ "title":"How many validated objects in this category in this project", @@ -5938,103 +6304,143 @@ }, "UserModel":{ "title":"UserModel", - "required":[ - "email", - "name" - ], "type":"object", "properties":{ "id":{ "title":"Id", - "type":"integer" + "type":"integer", + "description":"Unique user identifier", + "example":1 }, "email":{ "title":"Email", - "type":"string" + "type":"string", + "description":"User's email used during registration", + "example":"user@email.com" }, "name":{ "title":"Name", - "type":"string" + "type":"string", + "description":"User's full name", + "example":"userName" }, "organisation":{ "title":"Organisation", - "type":"string" + "type":"string", + "description":"User's organisation", + "example":"Oceanographic Laboratory of Villefranche sur Mer - LOV" }, "active":{ - "title":"Active", - "type":"boolean" + "title":"Account status", + "type":"boolean", + "description":"User's Account status", + "example":true }, "country":{ "title":"Country", - "type":"string" + "type":"string", + "description":"User's country", + "example":"France" }, "usercreationdate":{ - "title":"Usercreationdate", + "title":"User creation date", "type":"string", - "format":"date-time" + "description":"User account creation date", + "format":"date-time", + "example":"2020-11-05T12:31:48.299713" }, "usercreationreason":{ - "title":"Usercreationreason", - "type":"string" + "title":"User creation reason", + "type":"string", + "description":"The reason of creation of this user account", + "example":"Analysis of size and shapes of plastic particles" } } }, "UserModelWithRights":{ "title":"UserModelWithRights", - "required":[ - "email", - "name" - ], "type":"object", "properties":{ "id":{ "title":"Id", - "type":"integer" + "type":"integer", + "description":"Unique user identifier", + "example":1 }, "email":{ "title":"Email", - "type":"string" + "type":"string", + "description":"User's email used during registration", + "example":"user@email.com" }, "name":{ "title":"Name", - "type":"string" + "type":"string", + "description":"User's full name", + "example":"userName" }, "organisation":{ "title":"Organisation", - "type":"string" + "type":"string", + "description":"User's organisation", + "example":"Oceanographic Laboratory of Villefranche sur Mer - LOV" }, "active":{ - "title":"Active", - "type":"boolean" + "title":"Account status", + "type":"boolean", + "description":"User's Account status", + "example":true }, "country":{ "title":"Country", - "type":"string" + "type":"string", + "description":"User's country", + "example":"France" }, "usercreationdate":{ - "title":"Usercreationdate", + "title":"User creation date", "type":"string", - "format":"date-time" + "description":"User account creation date", + "format":"date-time", + "example":"2020-11-05T12:31:48.299713" }, "usercreationreason":{ - "title":"Usercreationreason", - "type":"string" + "title":"User creation reason", + "type":"string", + "description":"The reason of creation of this user account", + "example":"Analysis of size and shapes of plastic particles" }, "can_do":{ - "title":"Actions allowed to this user, 1=create project, 2=administrate the app", + "title":"User's permissions", "type":"array", "items":{ "type":"integer" }, - "default":[] + "description":"List of User's allowed actions : 1 create a project, 2 administrate the app, 3 administrate users, 4 create taxon", + "default":[], + "example":[ + 1, + 4 + ] }, "last_used_projects":{ - "title":"The last used projects for this user", + "title":"Last used projects", "type":"array", "items":{ "$ref":"#/components/schemas/ProjectSummaryModel" }, - "default":[] + "description":"List of User's last used projects", + "default":[], + "example":[ + { + "projid":3, + "title":"Zooscan point B" + }, + { + "projid":1, + "title":"Zooscan Tara Med" + } + ] } } }, diff --git a/py/API_models/crud.py b/py/API_models/crud.py index 5e0348d6..721dedf4 100644 --- a/py/API_models/crud.py +++ b/py/API_models/crud.py @@ -4,11 +4,12 @@ # # Models used in CRUD API_operations. # +from datetime import datetime from typing import Optional, Dict, Type, List, Any from typing_extensions import TypedDict -from BO.ColumnUpdate import ColUpdateList +from BO.ColumnUpdate import ColUpdateList, ColUpdate from BO.DataLicense import LicenseEnum from BO.Project import ProjectUserStats from BO.Sample import SampleTaxoStats @@ -42,8 +43,8 @@ class ProjectSummaryModel(BaseModel): - projid: int = Field(title="Project unique identifier") - title: str = Field(title="Project title") + projid: int = Field(title="Project Id", description="Project unique identifier", default=None, example=1) + title: str = Field(title="Project title", description="Project's title", default=None, example="Zooscan Tara Med") # We exclude free columns from base model, they will be mapped in a dedicated sub-entity @@ -59,71 +60,90 @@ class ProjectSummaryModel(BaseModel): class UserModel(_UserModelFromDB): # type:ignore - pass - - -class UserModelWithRights(_UserModelFromDB): # type:ignore - can_do: List[int] = Field(title="Actions allowed to this user, 1=create project, 2=administrate the app", - default=[]) - last_used_projects: List[ProjectSummaryModel] = Field(title="The last used projects for this user", - default=[]) - + id: int = Field(title="Id", description="Unique user identifier", default=None, example=1) + email: str = Field(title="Email", description="User's email used during registration", default=None, example="user@email.com") + name: str = Field(title="Name", description="User's full name", default=None, example="userName") + organisation: str = Field(title="Organisation", description="User's organisation", default=None, example="Oceanographic Laboratory of Villefranche sur Mer - LOV") + active: bool = Field(title="Account status", description="User's Account status", default=None, example=True) + country: str = Field(title="Country", description="User's country", default=None, example="France") + usercreationdate: datetime = Field(title="User creation date", description="User account creation date", default=None, example="2020-11-05T12:31:48.299713") + usercreationreason: str = Field(title="User creation reason", description="The reason of creation of this user account", default=None, example="Analysis of size and shapes of plastic particles") + #pass + + +class UserModelWithRights(UserModel): # type:ignore + can_do: List[int] = Field(title="User's permissions", description="List of User's allowed actions : 1 create a project, 2 administrate the app, 3 administrate users, 4 create taxon", default=[], example=[1,4]) + last_used_projects: List[ProjectSummaryModel] = Field(title="Last used projects", description="List of User's last used projects", default=[], + example= [ + { + "projid": 3, + "title": "Zooscan point B" + }, + { + "projid": 1, + "title": "Zooscan Tara Med" + } + ]) class _AddedToProject(BaseModel): - obj_free_cols: FreeColT = Field(title="Object free columns", - default={}) - sample_free_cols: FreeColT = Field(title="Sample free columns", - default={}) - acquisition_free_cols: FreeColT = Field(title="Acquisition free columns", - default={}) - process_free_cols: FreeColT = Field(title="Process free columns", - default={}) - init_classif_list: List[int] = Field(title="Favorite taxa used in classification", - default=[]) - - managers: List[UserModel] = Field(title="Managers of this project", - default=[]) - annotators: List[UserModel] = Field(title="Annotators of this project, if not manager", - default=[]) - viewers: List[UserModel] = Field(title="Viewers of this project, if not manager nor annotator", - default=[]) - instrument: Optional[str] = Field(title="This project's instrument. Transitory: if several of them, then coma-separated") - contact: Optional[UserModel] = Field( - title="The contact person is a manager who serves as the contact person for other users and EcoTaxa's managers.") - - highest_right: str = Field( - title="The highest right for requester on this project. One of 'Manage', 'Annotate', 'View'.", - default="") - license: LicenseEnum = Field(title="Data licence", - default=LicenseEnum.Copyright) + obj_free_cols: FreeColT = Field(title="Object free cols", description="Object free columns", default={}, example={"area": "n01", "esd": "n02"}) + sample_free_cols: FreeColT = Field(title="Sample free cols", description="Sample free columns", default={}, example={"barcode": "t01"}) + acquisition_free_cols: FreeColT = Field(title="Acquisition free cols", description="Acquisition free columns", default={}, example={"flash_delay": "t01"}) + process_free_cols: FreeColT = Field(title="Process free cols", description="Process free columns", default={}, example={"nb_images": "t01"}) + init_classif_list: List[int] = Field(title="Init classification list", description="Favorite taxa used in classification", default=[], example=[5,11493,11498,11509]) - # owner: UserModel = Field(title="Owner of this project") + managers: List[UserModel] = Field(title="Managers", description="Managers of this project", default=[]) + annotators: List[UserModel] = Field(title="Annotators", description="Annotators of this project, if not manager", default=[]) + viewers: List[UserModel] = Field(title="Viewers", description="Viewers of this project, if not manager nor annotator", default=[]) + instrument: Optional[str] = Field(title="Instrument", description="This project's instrument. Transitory: if several of them, then coma-separated", example="zooscan") + contact: Optional[UserModel] = Field(title="Contact", description="The contact person is a manager who serves as the contact person for other users and EcoTaxa's managers.") - class Config: - schema_extra = { - "example": { - "obj_free_cols": {"area": "n01", "esd": "n02"}, - "sample_free_cols": {"barcode": "t01"}, - "acquisition_free_cols": {"flash_delay": "t01"}, - "process_free_cols": {"nb_images": "t01"}, - } - } + highest_right: str = Field( title="Highest right", description="The highest right for requester on this project. One of 'Manage', 'Annotate', 'View'.", default="", example="View") + license: LicenseEnum = Field(title="License", description="Data licence", default=LicenseEnum.Copyright, example=LicenseEnum.CC_BY) + # owner: UserModel = Field(title="Owner of this project") # TODO: when python 3.7+, we can have pydantic generics and remove the ignore below class ProjectModel(_ProjectModelFromDB, _AddedToProject): # type:ignore """ Project + computed """ + projid:int = Field(title="Project Id", description="The project Id", example=4824) + title: str = Field(title="Title", description="The project title", example="MyProject") + visible: bool = Field(title="Visible", description="The project visibility", example=False) + status: str = Field(title="Status", description="The project status", example="Annotate") + objcount: float = Field(title="Object count", description="The number of objects", example=32292.0) + pctvalidated: float = Field(title="Percentage validated", description="Percentage of validated images.", example=0.015483711135885049) + pctclassified: float = Field(title="Percentage classified", description="Percentage of classified images.", example=100.0) + classifsettings: str = Field(title="Classification settings", description="", example="baseproject=1602\ncritvar=%area,angle,area,area_exc,bx,by,cdexc,centroids,circ.,circex,convarea,convperim,cv,elongation,esd,fcons,feret,feretareaexc,fractal,height,histcum1,histcum2,histcum3,intden,kurt,lat_end,lon_end,major,max,mean,meanpos,median,min,minor,mode,nb1,nb2,perim.,perimareaexc,perimferet,perimmajor,range,skelarea,skew,slope,sr,stddev,symetrieh,symetriehc,symetriev,symetrievc,thickr,width,x,xm,xstart,y,ym,ystart\nposttaxomapping=\nseltaxo=45074,84963,61990,13333,82399,61973,62005,25930,25932,61996,78426,81941,11514,85076,85061,30815,85185,92230,85079,84993,25824,85115,85004,26525,25944,11509,26524,92112,84976,25942,84980,85078,78418,84977,85060,61993,61991,85069,81871,74144,11758,72431,13381,11518,5,18758,85117,92042,84968,84997,87826,92236,92237,92039,84989,85193,83281,78412,92239,71617,81977,45071,12865,85044,81940,85067,12908,85116,56693,85008,92139,92068\nusemodel_foldername=testln1") + classiffieldlist: str = Field(title="Classification field list", description="", example="depth_min=depth_min\r\ndepth_max=depth_max\r\narea=area [pixel]\r\nmean=mean [0-255]\r\nfractal=fractal\r\nmajor=major [pixel]\r\nsymetrieh=symetrieh\r\ncirc.=circ\r\nferet = Feret [pixel]") + popoverfieldlist: str = Field(title="Pop over field list", description="", example="depth_min=depth_min\r\ndepth_max=depth_max\r\narea=area [pixel]\r\nmean=mean [0-255]\r\nfractal=fractal\r\nmajor=major [pixel]\r\nsymetrieh=symetrieh\r\ncirc.=circ\r\nferet = Feret [pixel]") + comments: str = Field(title="Comments", description="The project comments", example="") + projtype: str = Field(title="Project type", description="The type of the project", example="") + rf_models_used: str = Field(title="Rf models used", description="", example="") + cnn_network_id: str = Field(title="Cnn network id", description="", example="SCN_zooscan_group1") class SampleModel(_SampleModelFromDB): # type:ignore - free_columns: Dict[str, Any] = Field(title="Free columns from sample mapping in project", - default={}) + sampleid : int = Field(title="Sample Id", description="The sample Id", example=100) + projid : int = Field(title="Project Id", description="The project Id", example=4) + orig_id : str = Field(title="Origine Id", description="The origine Id", example="dewex_leg2_19") + latitude : float = Field(title="Latitude", description="The latitude", example=42.0231666666667) + longitude : float = Field(title="Longitude", description="The longitude", example=4.71766666666667) + dataportal_descriptor : str = Field(title="Dataportal descriptor", description="", example="") + free_columns: Dict[str, Any] = Field(title="Free columns", description="Free columns from sample mapping in project", + default={}, example={"flash_delay": "t01"}) SampleTaxoStatsModel = dataclass_to_model(SampleTaxoStats, add_suffix=True, - titles={'sample_id': "The sample id", + titles={'sample_id': "Sample id", + 'used_taxa': "Used taxa", + "nb_unclassified": "Number unclassified", + "nb_validated": "Number validated", + "nb_dubious": "Number dubious", + "nb_predicted": "Number predicted" + }, + descriptions={'sample_id': "The sample id", 'used_taxa': "The taxa/category ids used inside the sample. -1 for unclassified objects", "nb_unclassified": "The number of unclassified objects inside the sample", "nb_validated": "The number of validated objects inside the sample", @@ -133,8 +153,12 @@ class SampleModel(_SampleModelFromDB): # type:ignore class AcquisitionModel(_AcquisitionModelFromDB): # type:ignore - free_columns: Dict[str, Any] = Field(title="Free columns from acquisition mapping in project", - default={}) + acquisid : int = Field(title="Acquisition Id", description="The acquisition Id", example=144, default=None) + acq_sample_id : int = Field(title="Acquisition sample Id", description="The acquisition sample Id", example=1039, default=None) + orig_id : str = Field(title="Origine Id", description="The origine Id", example="uvp5_station1_cast1b", default=None) + instrument : str = Field(title="Instrument", description="Instrument used", example="uvp5", default=None) + free_columns: Dict[str, Any] = Field(title="Free columns", description="Free columns from acquisition mapping in project", + default={}, example={"bottomdepth": 322,"ship": "suroit"}) class ProcessModel(_ProcessModelFromDB): # type:ignore @@ -143,11 +167,9 @@ class ProcessModel(_ProcessModelFromDB): # type:ignore class CreateProjectReq(BaseModel): - clone_of_id: int = Field(title="If set, clone specified Project", - default=None) - title: str = Field(title="The project title") - visible: bool = Field(title="The project is created visible", - default=True) + clone_of_id: int = Field(title="Clone of id", description="If set, clone specified Project", default=None, example=2) + title: str = Field(title="Title", description="The project title", example="My new project title") + visible: bool = Field(title="Visible", description="If True, the project is created visible", default=True, example=True) class ProjectFilters(TypedDict, total=False): @@ -223,60 +245,82 @@ class ProjectFilters(TypedDict, total=False): class BulkUpdateReq(BaseModel): # TODO: A Union of possible types? - target_ids: List[int] = Field(title="The IDs of the target entities") - updates: ColUpdateList = Field(title="The updates, to do on all impacted entities") + target_ids: List[int] = Field(title="Target Id", description="The IDs of the target entities", example=[1,5,290]) + updates: ColUpdateList = Field(title="Updates", description="The list of updates, to do on all impacted entities. \n\n \ + { \n\n \ + ucol : A column name, pseudo-columns AKA free ones, are OK. \n\n \ + uval : The new value to set, always as a string \n\n \ + }", + example=[{"ucol": "sub_part", "uval": "2"}]) + # updates: List[ColUpdate] = Field(title="Updates", description="The updates, to do on all impacted entities") # TODO: Derive from ProjectTaxoStats class ProjectTaxoStatsModel(BaseModel): - projid: int = Field(title="The project id") - used_taxa: List[int] = Field(title="The taxa/category ids used inside the project." - " An id of -1 means some unclassified objects", default=[]) - nb_unclassified: int = Field(title="The number of unclassified objects inside the project") - nb_validated: int = Field(title="The number of validated objects inside the project") - nb_dubious: int = Field(title="The number of dubious objects inside the project") - nb_predicted: int = Field(title="The number of predicted objects inside the project") + projid: int = Field(title="projid", description = "The project id", example=1) + used_taxa: List[int] = Field(title="used_taxa", description = "The taxa/category ids used inside the project." + " An id of -1 means some unclassified objects", default=[], example=[45072, 78418, 84963, 85011, 85012, 85078]) + nb_unclassified: int = Field(title="nb_unclassified", description = "The number of unclassified objects inside the project", example=0) + nb_validated: int = Field(title="nb_validated", description = "The number of validated objects inside the project", example=5000) + nb_dubious: int = Field(title="nb_dubious", description = "The number of dubious objects inside the project", example=56) + nb_predicted: int = Field(title="nb_predicted", description = "The number of predicted objects inside the project", example=1345) + ProjectUserStatsModel = dataclass_to_model(ProjectUserStats, add_suffix=True, - titles={'projid': "The project id", - 'annotators': "The users who ever decided on classification or state of objects", - 'activities': "More details on annotators' activities"}) + titles={'projid': "Project id", + 'annotators': "Annotators", + 'activities': "Activities"}, + descriptions={ + 'projid': "The project id", + 'annotators': "The users who ever decided on classification or state of objects", + 'activities': "More details on annotators' activities" + }) class CreateCollectionReq(BaseModel): - title: str = Field(title="The collection title") - project_ids: List[int] = Field(title="The composing project IDs", min_items=1) + title: str = Field(title="Title", description="The collection title", + example="My collection") + project_ids: List[int] = Field(title="Project ids", description="The list of composing project IDs", + example=[1], min_items=1) class _AddedToCollection(BaseModel): """ What's added to Collection comparing to the plain DB record. """ - project_ids: List[int] = Field(title="The composing project IDs", min_items=1) - provider_user: Optional[UserModel] = Field(title="""Is the person who is responsible for the content of this metadata record. -Writer of the title and abstract.""") - contact_user: Optional[UserModel] = Field(title="""Is the person who should be contacted in cases of questions regarding the -content of the dataset or any data restrictions. This is also the person who is most likely to -stay involved in the dataset the longest.""") - creator_users: List[UserModel] = Field(title="""All people who are responsible for the creation of -the collection. Data creators should receive credit for their work and should therefore be -included in the citation.""", - default=[]) - creator_organisations: List[str] = Field(title="""All organisations who are responsible for the creation of - the collection. Data creators should receive credit for their work and should therefore be - included in the citation.""", - default=[]) - associate_users: List[UserModel] = Field(title="Other person(s) associated with the collection", - default=[]) - associate_organisations: List[str] = Field(title="Other organisation(s) associated with the collection", - default=[]) + project_ids: List[int] = Field(title="Project ids", description="The list of composing project IDs", + example=[1], min_items=1) + provider_user: Optional[UserModel] = Field(title="Provider user", description="""Is the person who + is responsible for the content of this metadata record. Writer of the title and abstract.""") + contact_user: Optional[UserModel] = Field(title="Contact user", description="""Is the person who + should be contacted in cases of questions regarding the content of the dataset or any data restrictions. + This is also the person who is most likely to stay involved in the dataset the longest.""") + creator_users: List[UserModel] = Field(title="Creator users", description="""All people who + are responsible for the creation of the collection. Data creators should receive credit + for their work and should therefore be included in the citation.""", default=[]) + creator_organisations: List[str] = Field(title="Creator organisations", description="""All + organisations who are responsible for the creation of the collection. Data creators should + receive credit for their work and should therefore be included in the citation.""", default=[]) + associate_users: List[UserModel] = Field(title="Associate users", description="""Other person(s) + associated with the collection""", default=[]) + associate_organisations: List[str] = Field(title="Associate organisations", description="""Other + organisation(s) associated with the collection""", default=[]) class CollectionModel(_CollectionModelFromDB, _AddedToCollection): # type:ignore """ Collection + computed """ + id: int = Field(title="Id", description="The collection Id", default=None, example=1) + external_id: str = Field(title="External Id", description="The external Id", default=None, example="") + external_id_system: str = Field(title="External id system", description="The external Id system", default=None, example="") + title: str = Field(title="Title", description="The collection title", default=None, example="My collection") + short_title: str = Field(title="Short title", description="The collection short title", default=None, example="My coll") + citation: str = Field(title="Citation", description="The collection citation", default=None, example="") + license: str = Field(title="License", description="The collection license", default=None, example=LicenseEnum.CC_BY) + abstract: str = Field(title="Abstract", description="The collection abstract", default=None, example="") + description: str = Field(title="Description", description="The collection description", default=None, example="") _JobModelFromDB = sqlalchemy_to_pydantic(Job, exclude=[Job.params.name, diff --git a/py/API_models/exports.py b/py/API_models/exports.py index 1ca8d2de..628096dc 100644 --- a/py/API_models/exports.py +++ b/py/API_models/exports.py @@ -20,12 +20,12 @@ class EMODnetExportRsp(BaseModel): """ EMODNet format export response. """ - errors: List[str] = Field(title="Showstopper problems found while building the archive.", - default=[]) - warnings: List[str] = Field(title="Problems found while building the archive, which do not prevent producing it.", - default=[]) - job_id: int = Field(title="The created job, 0 if there were problems.", - default=0) + errors: List[str] = Field(title="Errors", description="Showstopper problems found while building the archive.", + example=[], default=[]) + warnings: List[str] = Field(title="Warnings", description="Problems found while building the archive, which do not prevent producing it.", + example=[], default=[]) + job_id: int = Field(title="Job Id", description="The created job, 0 if there were problems.", + example=1, default=0) class ExportTypeEnum(str, Enum): diff --git a/py/API_models/helpers/DataclassToModel.py b/py/API_models/helpers/DataclassToModel.py index ffe8db6b..fa6a3e8c 100644 --- a/py/API_models/helpers/DataclassToModel.py +++ b/py/API_models/helpers/DataclassToModel.py @@ -24,7 +24,7 @@ class DataclassConfig(BaseConfig): T = TypeVar('T') -def dataclass_to_model(clazz: T, add_suffix: bool = False, titles: Optional[Dict[str, str]] = None) -> PydanticModelT: +def dataclass_to_model(clazz: T, add_suffix: bool = False, titles: Optional[Dict[str, str]] = None, descriptions: Optional[Dict[str, str]] = None ) -> PydanticModelT: model_fields = {} a_field: dataclasses.Field for a_field in dataclasses.fields(clazz): @@ -61,4 +61,10 @@ def dataclass_to_model(clazz: T, add_suffix: bool = False, titles: Optional[Dict for a_field_name, a_title in titles.items(): the_field: ModelField = ret.__fields__[a_field_name] the_field.field_info.title = a_title + if descriptions is not None: + # Amend with descriptions, for doc. Let crash (KeyError) if descriptions are not up-to-date with base. + for a_field_name, a_description in descriptions.items(): + the_field: ModelField = ret.__fields__[a_field_name] + the_field.field_info.description = a_description + return ret diff --git a/py/API_models/login.py b/py/API_models/login.py index 268d40fa..7c14bf77 100644 --- a/py/API_models/login.py +++ b/py/API_models/login.py @@ -9,5 +9,6 @@ class LoginReq(BaseModel): - username: str = Field("User email, like in Web UI") - password: str = Field("User password") + password: str = Field(title="User's password" , default=None, description="User password", example="UserPassword!") + username: str = Field(title="User's eamil", default=None, description="User email used during registration", example="user@email.com") + diff --git a/py/API_models/merge.py b/py/API_models/merge.py index e3bff4a8..324e2a56 100644 --- a/py/API_models/merge.py +++ b/py/API_models/merge.py @@ -15,5 +15,5 @@ class MergeRsp(BaseModel): """ Merge response. """ - errors: List[str] = Field(title="The errors found during processing.", + errors: List[str] = Field(title="Errors", description="The errors found during processing.", default=[]) diff --git a/py/API_models/subset.py b/py/API_models/subset.py index 4d104a14..a89630cf 100644 --- a/py/API_models/subset.py +++ b/py/API_models/subset.py @@ -21,16 +21,16 @@ class GroupDefinitions(str, Enum): class SubsetReq(BaseModel): """ Subset request. """ - filters: Dict[str, str] = Field(title="The filters to apply to project", default={}) - dest_prj_id: int = Field(title="The destination project ID.") - group_type: GroupDefinitions = Field(title="Define the groups in which to apply limits. C for categories, S for samples, A for acquisitions.") - limit_type: LimitMethods = Field(title="The type of limit_value: P for %, V for constant, both per group.") - limit_value: float = Field(title="Limit value, e.g. 20% or 5 per copepoda or 5% per sample.") - do_images: bool = Field(title="If set, also clone images.") + filters: Dict[str, str] = Field(title="Filters", description="The filters to apply to project", default={}, example={"freenum": "n01", "freenumst": "0"}) + dest_prj_id: int = Field(title="Destination project id", description="The destination project ID.", example=22) + group_type: GroupDefinitions = Field(title="Group type", description="Define the groups in which to apply limits. C for categories, S for samples, A for acquisitions.", example=GroupDefinitions.acquisitions) + limit_type: LimitMethods = Field(title="Limit type", description="The type of limit_value: P for %, V for constant, both per group.", example=LimitMethods.percent) + limit_value: float = Field(title="Limit value", description="Limit value, e.g. 20% or 5 per copepoda or 5% per sample.", example=10.0) + do_images: bool = Field(title="Do images", description="If set, also clone images.", example=True) class SubsetRsp(BaseModel): """ Subset response. """ - job_id: int = Field(title="The job created for this operation.") + job_id: int = Field(title="Job Id", description="The job created for this operation.",example=143) # errors: List[str] = Field(title="The errors found during processing", # default=[]) diff --git a/py/main.py b/py/main.py index 9e3068dd..6304e382 100644 --- a/py/main.py +++ b/py/main.py @@ -8,6 +8,8 @@ from logging import INFO from typing import Union, Tuple +from sqlalchemy.sql.expression import null + from fastapi import FastAPI, Request, Response, status, Depends, HTTPException, UploadFile, File, Query, Form from fastapi.logger import logger as fastapi_logger from fastapi.responses import StreamingResponse, FileResponse @@ -113,15 +115,26 @@ # noinspection PyUnusedLocal -@app.post("/login", tags=['authentification']) +@app.post( + "/login", + tags=['authentification'], + responses={ + 200 : { + "content": { + "application/json": { + "example": "eyJ1c2VyX2lkIjo5OTN9.YUmHHw.-X4tsLsYbwldKL6vDgO3o4-aAxE" + } + } + } + }, + response_model=str +) async def login(params: LoginReq) -> str: """ - Login barrier. If successful, the login will return a JWT which will have to be used - in Bearer authentication scheme for subsequent calls. - - -`username`: User *email* which was used during registration - - -`password`: User password + **Login barrier,** + + If successful, the login will returns a **JWT** which will have to be used + in bearer authentication scheme for subsequent calls. """ with LoginService() as sce: with RightsThrower(): @@ -131,7 +144,9 @@ async def login(params: LoginReq) -> str: @app.get("/users", tags=['users'], response_model=List[UserModel]) def get_users(current_user: int = Depends(get_current_user)): """ - Return the list of users. For admins only. + Returns the list of **all users** with their information. + + 🔒 *For admins only.* """ with UserService() as sce: return sce.list(current_user) @@ -140,11 +155,7 @@ def get_users(current_user: int = Depends(get_current_user)): @app.get("/users/me", tags=['users'], response_model=UserModelWithRights) def show_current_user(current_user: int = Depends(get_current_user)): """ - Return currently authenticated user. On top of DB fields, 'can_do' lists the allowed system-wide actions: - CREATE_PROJECT = 1 - ADMINISTRATE_APP = 2 - ADMINISTRATE_USERS = 3 - CREATE_TAXON = 4 + Returns **currently authenticated user's** information, permissions and last used projects. """ with UserService() as sce: ret = sce.search_by_id(current_user, current_user) @@ -156,26 +167,51 @@ def show_current_user(current_user: int = Depends(get_current_user)): return ret -@app.get("/users/my_preferences/{project_id}", tags=['users'], response_model=str) +@app.get( + "/users/my_preferences/{project_id}", + tags=['users'], + responses={ + 200 : { + "content": { + "application/json": { + "example": "{\"dispfield\": \" dispfield_orig_id dispfield_classif_auto_score dispfield_classif_when\", \"ipp\": \"1000\", \"magenabled\": \"1\", \"popupenabled\": \"1\", \"sortby\": \"orig_id\", \"sortorder\": \"asc\", \"statusfilter\": \"P\", \"zoom\": \"90\"}" + } + } + } + }, + response_model=str) def get_current_user_prefs(project_id: int, - key: str, + key: str= Query(default=None,title="Key", description="The preference key.", example="filters"), current_user: int = Depends(get_current_user)) -> str: """ - Return one preference, for project and currently authenticated user. + **Returns one preference**, for a project and the currently authenticated user. + + Available keys are **cwd**, **img_import** and **filters**. """ with UserService() as sce: return sce.get_preferences_per_project(current_user, project_id, key) -@app.put("/users/my_preferences/{project_id}", tags=['users']) +@app.put("/users/my_preferences/{project_id}", tags=['users'], + responses={ + 200 : { + "content": { + "application/json": { + "example": null + } + } + } + }) def set_current_user_prefs(project_id: int, - key: str, - value: str, + key: str = Query(default=None,title="Key", description="The preference key.", example="filters"), + value: str = Query(default=None,title="Value", description="The value to set this preference to.", example="{\"dispfield\": \" dispfield_orig_id dispfield_classif_auto_score dispfield_classif_when dispfield_random_value\", \"ipp\": \"500\", \"magenabled\": \"1\", \"popupenabled\": \"1\", \"sortby\": \"orig_id\", \"sortorder\": \"asc\", \"statusfilter\": \"\", \"zoom\": \"90\"}"), current_user: int = Depends(get_current_user)): """ - Set one preference, for project and currently authenticated user. - -`key`: The preference key - -`value`: The value to set this preference to. + **Sets one preference**, for a project and for the currently authenticated user. + + Available keys are **cwd**, **img_import** and **filters**. + + The key disappears if set to empty string. """ with UserService() as sce: return sce.set_preferences_per_project(current_user, project_id, key, value) @@ -183,9 +219,9 @@ def set_current_user_prefs(project_id: int, @app.get("/users/search", tags=['users'], response_model=List[UserModel]) def search_user(current_user: int = Depends(get_current_user), - by_name: Optional[str] = None): + by_name: Optional[str] = Query(default=None, title="search by name", description="Search by name, use % for searching with 'any char'.", example="%userNa%")): """ - Search users using various criteria, search is case insensitive and might contain % chars. + **Search users using various criteria**, search is case insensitive and might contain % chars. """ with UserService() as sce: ret = sce.search(current_user, by_name) @@ -196,7 +232,7 @@ def search_user(current_user: int = Depends(get_current_user), def get_user(user_id: int, current_user: int = Depends(get_current_user)): """ - Return a single user by its id. + Returns **information about the user** corresponding to the given id. """ with UserService() as sce: ret = sce.search_by_id(current_user, user_id) @@ -207,13 +243,26 @@ def get_user(user_id: int, # ######################## END OF USER -@app.post("/collections/create", tags=['collections']) +@app.post("/collections/create", + tags=['collections'], + responses={ + 200 : { + "content": { + "application/json": { + "example": 1 + } + } + } + }, + response_model = int) def create_collection(params: CreateCollectionReq, current_user: int = Depends(get_current_user)) -> Union[int, str]: """ - Create a collection with at least one project inside. + **Create a collection** with at least one project inside. + + Returns the created collection Id. - *Currently only for admins* + 🔒 *For admins only.* """ with CollectionsService() as sce: with RightsThrower(): @@ -225,13 +274,12 @@ def create_collection(params: CreateCollectionReq, @app.get("/collections/search", tags=['collections'], response_model=List[CollectionModel]) -def search_collections(title: str, +def search_collections(title: str= Query(default=None, title="Title", description="Search by title, use % for searching with 'any char'.", example="%coll%"), current_user: int = Depends(get_current_user)): """ - Search for collections. - Use % for searching with 'any char'. - - *Currently only for admins* + **Search for collections.** + + 🔒 *For admins only.* """ with CollectionsService() as sce: with RightsThrower(): @@ -240,11 +288,13 @@ def search_collections(title: str, @app.get("/collections/by_title", tags=['collections'], response_model=CollectionModel) -def collection_by_title(q: str): +def collection_by_title(q: str= Query(default=None, title="Title", description="Search by **exact** title", example="My collection")): """ - Return the single collection with this title. - For published datasets. - !!! DO NOT MODIFY BEHAVIOR !!! + Return the **single collection with this title**. + + *For published datasets.* + + ⚠️ DO NOT MODIFY BEHAVIOR ⚠️ """ with CollectionsService() as sce: with RightsThrower(): @@ -253,11 +303,13 @@ def collection_by_title(q: str): @app.get("/collections/by_short_title", tags=['collections'], response_model=CollectionModel) -def collection_by_short_title(q: str): +def collection_by_short_title(q: str= Query(default=None, title="Short title", description="Search by **exact** short title", example="My coll")): """ - Return the single collection with this title. - For published datasets. - !!! DO NOT MODIFY BEHAVIOR !!! + Return the **single collection with this short title**. + + *For published datasets.* + + ⚠️ DO NOT MODIFY BEHAVIOR ⚠️ """ with CollectionsService() as sce: with RightsThrower(): @@ -269,9 +321,9 @@ def collection_by_short_title(q: str): def get_collection(collection_id: int, current_user: int = Depends(get_current_user)): """ - Read a collection by its ID. + Returns **information about the collection** corresponding to the given id. - *Currently only for admins* + 🔒 *For admins only.* """ with CollectionsService() as sce: with RightsThrower(): @@ -281,15 +333,24 @@ def get_collection(collection_id: int, return present_collection -@app.put("/collections/{collection_id}", tags=['collections']) +@app.put("/collections/{collection_id}", tags=['collections'], + responses={ + 200 : { + "content": { + "application/json": { + "example": null + } + } + } + }) def update_collection(collection_id: int, collection: CollectionModel, current_user: int = Depends(get_current_user)): """ - Update the collection. Note that some updates are silently failing when not compatible + **Update the collection**. Note that some updates are silently failing when not compatible with the composing projects. - *Currently only for admins* + 🔒 *For admins only.* """ with CollectionsService() as sce: with RightsThrower(): @@ -311,36 +372,44 @@ def update_collection(collection_id: int, @app.get("/collections/{collection_id}/export/emodnet", tags=['collections'], response_model=EMODnetExportRsp) def emodnet_format_export(collection_id: int, - dry_run: bool, - with_zeroes: bool, - auto_morpho: bool, - with_computations: bool, + dry_run: bool = Query(default=None, title="Dry run", description="If set, then only a diagnostic of doability will be done.", example=False), + with_zeroes: bool = Query(default=None, title="With zeroes", description="If set, then *absent* records will be generated, in the relevant samples, for categories present in other samples.", example=False), + auto_morpho: bool = Query(default=None, title="Auto morpho", description="If set, then any object classified on a Morpho category will be added to the count of the nearest Phylo parent, upward in the tree.", example=False), + with_computations: bool = Query(default=None, title="With computations", description="If set, then an attempt will be made to compute organisms concentrations and biovolumes.", example=False), current_user: int = Depends(get_current_user)) -> EMODnetExportRsp: """ - Export the collection in EMODnet format, @see https://www.emodnet-ingestion.eu/ + **Export the collection in EMODnet format**, @see https://www.emodnet-ingestion.eu + Produces a DwC-A archive into a temporary directory, ready for download. - - param `dry_run`: If set, then only a diagnostic of doability will be done. - - param `with_zeroes`: If set, then *absent* records will be generated, in the relevant samples, - for categories present in other samples. - - param `with_computations`: If set, then an attempt will be made to compute organisms concentrations - and biovolumes. - - param `auto_morpho`: If set, then any object classified on a Morpho category will be added to - the count of the nearest Phylo parent, upward in the tree. Maybe useful, a reader in Python: https://python-dwca-reader.readthedocs.io/en/latest/index.html - *Currently only for admins* + 🔒 *For admins only.* """ with EMODnetExport(collection_id, dry_run, with_zeroes, with_computations, auto_morpho) as sce: with RightsThrower(): return sce.run(current_user) -@app.delete("/collections/{collection_id}", tags=['collections']) +@app.delete("/collections/{collection_id}", tags=['collections'], + responses={ + 200 : { + "content": { + "application/json": { + "example": 0 + } + } + } + }, + response_model= int) def erase_collection(collection_id: int, current_user: int = Depends(get_current_user)) -> int: """ - Delete the collection, i.e. the precious fields, as the projects are just linked-at from the collection. + **Delete the collection**, + + i.e. the precious fields, as the projects are just linked-at from the collection. + + 🔒 *For admins only.* """ with CollectionsService() as sce: with RightsThrower(): @@ -358,27 +427,18 @@ def erase_collection(collection_id: int, # TODO TODO TODO: No verification of GET query parameters by FastAPI. pydantic does POST models OK. @app.get("/projects/search", tags=['projects'], response_model=List[ProjectModel]) def search_projects(current_user: Optional[int] = Depends(get_optional_current_user), - also_others: bool = Query(default=False, deprecated=True), - not_granted: bool = False, - for_managing: bool = False, - title_filter: str = '', - instrument_filter: str = '', - filter_subset: bool = False, - order_field: Optional[str] = Query(default=None, - description="One of %s" % list(project_model_columns.keys())), - window_start: Optional[int] = Query(default=None, - description="Skip `window_start` before returning data"), - window_size: Optional[int] = Query(default=None, - description="Return only `window_size` lines") + also_others: bool = Query(default=False, deprecated=True, title="Also others", description="", example=False), + not_granted: bool = Query(default=False, title="Not granted", description="Return projects on which the current user has _no permission_, but visible to him/her", example=False), + for_managing: bool = Query(default=False, title="Nor managing", description="Return projects that can be written to (including erased) by the current user", example=False), + title_filter: str = Query(default=None, title="Title filter", description="Use this pattern for matching returned projects names", example="Tara"), + instrument_filter: str = Query(default='', title="Instrument filter", description="Only return projects where this instrument was used", example="uvp5"), + filter_subset: bool = Query(default=False, title="Filter subset", description="Only return projects having 'subset' in their names", example=True), + order_field: Optional[str] = Query(default=None, title="Order field", description="One of %s" % list(project_model_columns.keys()), example="instrument"), + window_start: Optional[int] = Query(default=None, title="Window start", description="Skip `window_start` before returning data", example="0"), + window_size: Optional[int] = Query(default=None, title="Window size", description="Return only `window_size` lines", example="100"), ) -> MyORJSONResponse: # List[ProjectBO] """ - Return projects which the current user has explicit permission to access, with search options. - - `param` not_granted: Return projects on which the current user has _no permission_, but visible to him/her - - `param` for_managing: Return projects that can be written to (including erased) by the current user - - `param` title_filter: Use this pattern for matching returned projects names - - `param` instrument_filter: Only return projects where this instrument was used - - `param` filter_subset: Only return projects having 'subset' in their names - - `params` order_field, window_start, window_size: See accompanying description. + Returns **projects which the current user has explicit permission to access, with search options.** Note that, for performance reasons, in returned ProjectModels, field 'highest_rank' is NOT valued (unlike in simple query). The same information can be found in 'managers', 'annotators' and 'viewers' lists. @@ -392,13 +452,25 @@ def search_projects(current_user: Optional[int] = Depends(get_optional_current_u return MyORJSONResponse(ret) -@app.post("/projects/create", tags=['projects']) +@app.post("/projects/create", tags=['projects'], +responses={ + 200 : { + "content": { + "application/json": { + "example": 44 + } + } + } + }, + response_model= Union[int, str]) def create_project(params: CreateProjectReq, current_user: int = Depends(get_current_user)) -> Union[int, str]: """ - Create an empty project with only a title, and return its number. + **Create an empty project with only a title,** and **return its id**. + The project will be managed by current user. - The user has to be app administrator or project creator. + + 🔒 The user has to be *app administrator* or *project creator*. """ with ProjectsService() as sce: with RightsThrower(): @@ -411,10 +483,10 @@ def create_project(params: CreateProjectReq, @app.post("/projects/{project_id}/subset", tags=['projects'], response_model=SubsetRsp) def project_subset(project_id: int, - params: SubsetReq, + params: SubsetReq , current_user: int = Depends(get_current_user)): """ - Subset a project into another one. + **Subset a project into another one.** """ with SubsetServiceOnProject(project_id, params) as sce: with RightsThrower(): @@ -424,10 +496,10 @@ def project_subset(project_id: int, @app.get("/projects/{project_id}", tags=['projects'], response_model=ProjectModel) def project_query(project_id: int, - for_managing: Optional[bool] = False, + for_managing: Optional[bool] = Query(title= "For managinig", description="For managing this project.", default=None, example=False), current_user: Optional[int] = Depends(get_optional_current_user)) -> ProjectBO: """ - Read project if it exists for current user, eventually for managing it. + **Returns project** if it exists for current user, eventually for managing it. """ with ProjectsService() as sce: for_managing = bool(for_managing) @@ -437,16 +509,12 @@ def project_query(project_id: int, @app.get("/project_set/taxo_stats", tags=['projects'], response_model=List[ProjectTaxoStatsModel]) # type: ignore -def project_set_get_stats(ids: str, - taxa_ids: Optional[str] = "", +def project_set_get_stats(ids: str = Query(title="Ids", description="String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project.", default=None, example="1"), + taxa_ids: Optional[str] = Query(title="Taxa Ids", description="**If several taxa_ids are provided**, one stat record will be returned per requested taxa, if populated.\n \n **If taxa_ids is all**, all valued taxa in the project(s) are returned.", default="", example="all"), current_user: Optional[int] = Depends(get_optional_current_user) ) -> MyORJSONResponse: # List[ProjectTaxoStats] """ - Read projects statistics, i.e. used taxa and classification states. - - If several `ìds` are provided, one stat record will be returned per project. - If several `taxa_ids` are provided, one stat record will be returned per requested taxa, if populated. - If `taxa_ids` is 'all', all valued taxa in the project(s) are returned. + **Returns projects statistics**, i.e. used taxa and classification states. """ with ProjectsService() as sce: num_prj_ids = _split_num_list(ids) @@ -459,13 +527,36 @@ def project_set_get_stats(ids: str, return MyORJSONResponse(ret) -@app.get("/project_set/user_stats", tags=['projects'], response_model=List[ProjectUserStatsModel]) # type: ignore -def project_set_get_user_stats(ids: str, - current_user: int = Depends(get_current_user)) -> List[ProjectUserStats]: - """ - Read projects user statistics, i.e. a summary of the work done by users in the - required projects. The returned values are a detail _per project_, so size of input list - equals size of output list. +@app.get("/project_set/user_stats", tags=['projects'], +responses={ + 200 : { + "content": { + "application/json": { + "example": [{ + "projid":1, + "annotators":[{ + "id":1267, + "name":"User Name" + }], + "activities":[{ + "id":1267, + "nb_actions":605, + "last_annot":"2021-09-27T13:08:54" + }] + }] + } + } + } + }, response_model=List[ProjectUserStatsModel]) # type: ignore +def project_set_get_user_stats(ids: str = Query(title="Ids", + description="String containing the list of one or more id separated by non-num char. \n \n **If several ids are provided**, one stat record will be returned per project.", + default=None, example="1"), + current_user: int = Depends(get_current_user)) -> List[ProjectUserStats]: + """ + **Returns projects user statistics**, i.e. a summary of the work done by users in the + required projects. + + The returned values are a detail per project, so size of input list equals size of output list. """ with ProjectsService() as sce: num_ids = _split_num_list(ids) @@ -490,24 +581,39 @@ def project_dump(project_id: int, @app.post("/projects/{project_id}/merge", tags=['projects'], response_model=MergeRsp) def project_merge(project_id: int, - source_project_id: int, - dry_run: bool, + source_project_id: int = Query(title="Source project Id", description="Id of the other project. This source project will see all its objects gone and will be erased.", default=None, example=2), + dry_run: bool = Query(title="Dry run", description="If set, then only a diagnostic of doability will be done.", default=None, example=True), current_user: int = Depends(get_current_user)) -> MergeRsp: """ - Merge another project into this one. It's more a phagocytosis than a merge, as the source will see + **Merge another project into this one.** + + It's more a phagocytosis than a merge, as the source will see all its objects gone and will be erased. - - param `dry_run`: If set, then only a diagnostic of doability will be done. """ with MergeService(project_id, source_project_id, dry_run) as sce: with RightsThrower(): return sce.run(current_user) -@app.get("/projects/{project_id}/check", tags=['projects']) +@app.get("/projects/{project_id}/check", tags=['projects'], + responses={ + 200 : { + "content": { + "application/json": { + "example": [] + } + } + } + }, + response_model=List[str] +) def project_check(project_id: int, - current_user: int = Depends(get_current_user)): + current_user: int = Depends(get_current_user))->List[str]: """ - Check consistency of a project. + **Check consistency of a project**. + + With time and bugs, some consistency problems could be introduced in projects. + This service aims at listing them. """ with ProjectConsistencyChecker(project_id) as sce: with RightsThrower(): @@ -525,11 +631,22 @@ def project_stats(project_id: int, return sce.run(current_user) -@app.post("/projects/{project_id}/recompute_geo", tags=['projects']) +@app.post("/projects/{project_id}/recompute_geo", tags=['projects'], + responses={ + 200 : { + "content": { + "application/json": { + "example": null + } + } + } + }) def project_recompute_geography(project_id: int, current_user: int = Depends(get_current_user)) -> None: """ - Recompute geography information for all samples in project. + **Recompute geography information** for all samples in project. + + 🔒 The user has to be *project manager*. """ with ProjectsService() as sce: with RightsThrower(): @@ -566,28 +683,51 @@ def simple_import(project_id: int, return ret -@app.delete("/projects/{project_id}", tags=['projects']) +@app.delete("/projects/{project_id}", tags=['projects'], + responses={ + 200 : { + "content": { + "application/json": { + "example": (100, 0, 10, 10) + } + } + } + }) def erase_project(project_id: int, - only_objects: bool = False, + only_objects: bool = Query(title="Only objects", description="If set, the project structure is kept, but emptied from any object, sample, acquisition and process.", example=False, default = False), current_user: int = Depends(get_current_user)) -> Tuple[int, int, int, int]: """ - Delete the project. - Optionally, if "only_objects" is set, the project structure is kept, - but emptied from any object/sample/acquisition/process - Otherwise, no trace of the project will remain in the database. + **Delete the project.** + + Optionally, if "only_objects" is set, the project structure is kept, + but emptied from any object, sample, acquisition and process. + + Otherwise, no trace of the project will remain in the database. + + **Returns** the number of : **deleted objects**, 0, **deleated image rows** and **deleated image files**. """ with ProjectsService() as sce: with RightsThrower(): return sce.delete(current_user, project_id, only_objects) -@app.put("/projects/{project_id}", tags=['projects']) +@app.put("/projects/{project_id}", tags=['projects'], + responses={ + 200 : { + "content": { + "application/json": { + "example": null + } + } + } + }) def update_project(project_id: int, project: ProjectModel, current_user: int = Depends(get_current_user)): """ - Update the project. - Note that some fields will NOT be updated and simply ignored, e.g. *free_cols. + **Update the project**. + + Note that some fields will **NOT** be updated and simply ignored, e.g. *free_cols*. """ with ProjectsService() as sce: with RightsThrower(): @@ -612,15 +752,12 @@ def update_project(project_id: int, # ######################## END OF PROJECT @app.get("/samples/search", tags=['samples'], response_model=List[SampleModel]) -def samples_search(project_ids: str, - id_pattern: str, +def samples_search(project_ids: str = Query(default=None, title="Project Ids", description="String containing the list of one or more project id separated by non-num char.", example="1,55"), + id_pattern: str = Query(default=None, title="Pattern Id", description="Sample id textual pattern. Use * or '' for 'any matches'. Match is case-insensitive.", example="*"), current_user: Optional[int] = Depends(get_optional_current_user)) \ -> List[SampleBO]: """ - Read samples for a set of projects. - - - project_ids: any(non number)-separated list of project numbers - - id_pattern: sample id textual pattern. Use * or '' for 'any matches'. Match is case-insensitive. + **Search for samples** """ with SamplesService() as sce: proj_ids = _split_num_list(project_ids) @@ -629,15 +766,29 @@ def samples_search(project_ids: str, return ret -@app.get("/sample_set/taxo_stats", tags=['samples'], response_model=List[SampleTaxoStatsModel]) # type:ignore -def sample_set_get_stats(sample_ids: str, +@app.get("/sample_set/taxo_stats", tags=['samples'], + responses={ + 200 : { + "content": { + "application/json": { + "example": {'nb_dubious': 56, + 'nb_predicted': 5500, + 'nb_unclassified': 0, + 'nb_validated': 1345, + 'projid': 1, + 'used_taxa': [45072, 78418, 84963, 85011, 85012, 85078] + } + } + } + } + }, response_model=List[SampleTaxoStatsModel]) # type:ignore +def sample_set_get_stats(sample_ids: str = Query(default=None, title="Sample Ids", description="String containing the list of one or more sample ids separated by non-num char.", example="15,5"), current_user: Optional[int] = Depends(get_optional_current_user)) \ -> List[SampleTaxoStats]: """ - Read classification statistics for a set of samples. - EXPECT A SLOW RESPONSE. No cache of such information anywhere. + Returns **classification statistics** for the given set of samples. - - sample_ids: any(non number)-separated list of sample numbers + EXPECT A SLOW RESPONSE : No cache of such information anywhere. """ with SamplesService() as sce: sample_ids = _split_num_list(sample_ids) @@ -646,13 +797,25 @@ def sample_set_get_stats(sample_ids: str, return ret -@app.post("/sample_set/update", tags=['samples']) +@app.post("/sample_set/update", tags=['samples'], + responses={ + 200 : { + "content": { + "application/json": { + "example": 1 + } + } + } + }, + response_model = int) def update_samples(req: BulkUpdateReq, current_user: int = Depends(get_current_user)) -> int: """ - Do the required update for each sample in the set. Any non-null field in the model is written to - every impacted sample. - Return the number of updated entities. + Do the required **update for each sample in the set.** + + Any non-null field in the model is written to every impacted sample. + + **Returns the number of updated entities.** """ with SamplesService() as sce: with RightsThrower(): @@ -664,7 +827,7 @@ def sample_query(sample_id: int, current_user: Optional[int] = Depends(get_optional_current_user)) \ -> SampleBO: """ - Read a single object. + Returns **information about the sample** corresponding to the given id. """ with SamplesService() as sce: with RightsThrower(): @@ -677,11 +840,11 @@ def sample_query(sample_id: int, # ######################## END OF SAMPLE @app.get("/acquisitions/search", tags=['acquisitions'], response_model=List[AcquisitionModel]) -def acquisitions_search(project_id: int, +def acquisitions_search(project_id: int = Query(title="Project id", description="The project id", default=None, example=1), current_user: Optional[int] = Depends(get_optional_current_user)) \ -> List[AcquisitionBO]: """ - Read all acquisitions for a project. + Returns the **list of all acquisitions for a given project**. """ with AcquisitionsService() as sce: with RightsThrower(): @@ -689,12 +852,24 @@ def acquisitions_search(project_id: int, return ret -@app.post("/acquisition_set/update", tags=['acquisitions']) +@app.post("/acquisition_set/update", + tags=['acquisitions'], + responses={ + 200 : { + "content": { + "application/json": { + "example": 2 + } + } + } + }, + response_model=int) def update_acquisitions(req: BulkUpdateReq, current_user: int = Depends(get_current_user)) -> int: """ - Do the required update for each acquisition in the set. - Return the number of updated entities. + Do the required **update for each acquisition in the set**. + + Return the number of updated entities. """ with AcquisitionsService() as sce: with RightsThrower(): @@ -706,7 +881,7 @@ def acquisition_query(acquisition_id: int, current_user: Optional[int] = Depends(get_optional_current_user)) \ -> AcquisitionBO: """ - Read a single object. + Returns **information about the acquisition** corresponding to the given id. """ with AcquisitionsService() as sce: with RightsThrower(): @@ -718,11 +893,28 @@ def acquisition_query(acquisition_id: int, # ######################## END OF ACQUISITION -@app.get("/instruments/", tags=['instrument'], response_model=List[str]) -def instrument_query(project_ids: str) \ +@app.get("/instruments/", + tags=['instrument'], + response_model=List[str], + responses={ + 200 : { + "content": { + "application/json": { + "example": [ + "uvp5", + "zooscan" + ] + } + } + } + } +) +def instrument_query(project_ids: str = Query(title="Projects ids", + description="String containing the list of one or more project id separated by non-num char.", + default=None, example="1,2,3")) \ -> List[str]: """ - Query for instruments, inside specific project(s). + Returns the list of instruments, inside specific project(s). """ with InstrumentsService() as sce: proj_ids = _split_num_list(project_ids) @@ -1448,8 +1640,7 @@ def system_error(_current_user: int = Depends(get_current_user)): assert False -@app.get("/noop", tags=['misc'], response_model=Union[ObjectHeaderModel, # type: ignore - HistoricalClassificationModel]) +@app.get("/noop", tags=['misc'], response_model=Union[ObjectHeaderModel,HistoricalClassificationModel]) # type: ignore def do_nothing(_current_user: int = Depends(get_current_user)): """ This entry point will just do nothing.