From 21d04efe4ea17734f264fe6cc597ace3b237f57a Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 2 Feb 2024 08:35:19 +1100 Subject: [PATCH 1/4] bumping to newer action versions that use Node 20.x (#668) --- .github/workflows/test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1061ddc50..54176c391 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,13 +16,13 @@ jobs: run: shell: bash -eo pipefail -l {0} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: "temurin" # See 'Supported distributions' for available options java-version: "17" From 5f5b4bca5a33d3533ba5775b3de7ad9786ac345a Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 2 Feb 2024 12:44:12 +1100 Subject: [PATCH 2/4] docs: Refined the documentation to provide more context and clarity (#654) * added initial docs and cleaned up the instructions. * added purpose * fixed linting * docs: consolidated into main README and removed redundant files * docs: added pnpm and nvm lazy loading instructions --- README.md | 463 +++------------------------- docs/installation.md | 421 +++++++++++++++++++++++++ resources/2024-01-15_db-diagram.png | Bin 0 -> 148500 bytes 3 files changed, 457 insertions(+), 427 deletions(-) create mode 100644 docs/installation.md create mode 100644 resources/2024-01-15_db-diagram.png diff --git a/README.md b/README.md index ec3150ae4..5916d3282 100644 --- a/README.md +++ b/README.md @@ -1,453 +1,62 @@ -# Sample Metadata +# Metamist [![codecov](https://codecov.io/gh/populationgenomics/metamist/branch/dev/graph/badge.svg?token=OI3XZYR9HK)](https://codecov.io/gh/populationgenomics/metamist) -Metamist is database that stores **de-identified** -omics metadata. -There are three components to the metamist system: +## Introduction -- System-versioned MariaDB database, -- Python web API to manage permissions, and store frequently used queries, - - Including a GraphQL API for querying the database -- An installable python library that wraps the Python web API (using OpenAPI generator) +**Metamist** is a database designed for storing **de-identified** -omics metadata. -Every resource in metamist belongs to a project. All resources are access -controlled through membership of the google groups: -`$dataset-sample-metadata-main-{read,write}`. Note that members of google-groups -are cached in a blob as group-membership identity lookups are slow. +## Purpose -## API +The project provides an interface to interact with the -omics database via the Python client as well as the GraphQL + HTTP APIs. -There are two ways to query metamist in Python: +## Features -1. Use the REST interface with the predefined requests -2. Use the GraphQL interface. +- **Project-Based Resource Organization**: Every resource in Metamist is associated with a specific project. +- **Access Control**: Access to resources is controlled through membership in specific Google Groups: + - `dataset-sample-metadata-main-read`: For read-only access. + - `dataset-sample-metadata-main-write`: For write access. +- **Efficiency Note**: Members of Google Groups are cached in a blob to optimize performance, as group-membership identity lookups can be slow. -To use the GraphQL interface in Python with the `sample_metadata` library, you can do the following: +## High-Level Architecture -```python -from sample_metadata.graphql import query +It comprises three key components: -_query = """ -query YourQueryNameHere($sampleId: String!) { - sample(id: $sampleId) { - id - externalId - } -} -""" +1. **System-Versioned MariaDB Database**: A robust database system for managing -omics metadata. -print(query(_query, {"sampleId": "CPG18"})) -``` +2. **Python Web API**: This component is responsible for: + - Managing permissions. + - Storing frequently used queries. + - Providing a GraphQL/HTTP API for efficient querying of the database. -## Structure +3. **Installable Python Library**: Wraps the Python Web API using the OpenAPI generator, facilitating easier interaction with the system. -![Database structure](resources/2021-10-27_db-diagram.png) +### Schema -### Sample IDs +As of Jan 15, 2024 this schema should reflect the data structure on the tables: -In an effort to reduce our dependency on potentially mutable external sample IDs with inconsistent format, -the metamist server generates an internal sample id for every sample. Internally they're an -incrementing integer, but they're transformed externally to have a prefix, and checksum - this allows durability -when transcribing sample IDs to reduce mistypes, and allow to quickly check whether a sample ID is valid. +![Database Structure](resources/2024-01-15_db-diagram.png) -> NB: The prefix and checksums are modified per environment (production, development, local) to avoid duplicates from these environments. +You can also find this at [DbDiagram](https://dbdiagram.io/d/Metamist-Schema-v6-6-2-65a48ac7ac844320aee60d16). -For example, let's consider the production environment which uses the prefix of `CPG` and a checksum offset of 0: +The codebase contains the following modules worth noting: -> A sample is given the internal ID `12345`, we calculate the Luhn checksum to be `5` (with no offset applied). -> We can then concatenate the results, for the final sample ID to be `CPG123455`. +- `models` -> General data models + enums +- `db/python/tables` -> Interaction with MariaDB / BigQuery +- `db/python/layers` -> Logic +- `api/graphql` : GraphQL +- `api/routes`: HTTP + OpenAPI -### Reporting sex +And metamist maintains two clients: -To avoid ambiguity in reporting of gender, sex and karyotype - the sample metadata system -stores these values separately on the `participant` as: +- `web` -> React app that consumes a generated Typescript API + GraphQL +- `metamist` -> autogenerated Python API -- `reported_gender` (string, expected `male` | `female` | _other values_) -- `reported_sex` (follows pedigree convention: `unknown=0 | null`, `male=1`, `female=2`) -- `inferred_karyotype` (string, eg: `XX` | `XY` | _other karyotypes_) +## Installation and Running Locally -If you import a pedigree, the sex value is written to the `reported_sex` attribute. +- [Installation and developer setup](docs/installation.md) -## Local develompent of SM +## License -The recommended way to develop the metamist system is to run a local copy of SM. - -> There have been some reported issues of running a local SM environment on an M1 mac. - -You can run MariaDB with a locally installed docker, or from within a docker container. -You can configure the MariaDB connection with environment variables. - -### Creating the environment - -Python dependencies for the `metamist` API package are listed in `setup.py`. -Additional dev requirements are listed in `requirements-dev.txt`, and packages for -the sever-side code are listed in `requirements.txt`. - -We _STRONGLY_ encourage the use of `pyenv` for managing Python versions. -Debugging and the server will run on a minimum python version of 3.10. - -To setup the python environment, you can run: - -```shell -virtualenv venv -source venv/bin/activate -pip install -r requirements.txt -pip install -r requirements-dev.txt -pip install --editable . -``` - -### Extra software - -You'll need to install the following software to develop metamist: - -- Node / NPM (recommend using nvm) -- MariaDB (using MariaDB in docker is also good) -- Java (for liquibase / openapi-generator) -- Liquibase -- OpenAPI generator -- wget (optional) - -Our recommendation is in the following code block: - -```shell -brew install wget -brew install java -brew install liquibase -``` - -Add the following to your `.zshrc` file: - -```shell - -# homebrew should export this on an M1 Mac -# the intel default is /usr/local -export HB_PREFIX=${HOMEBREW_PREFIX-/usr/local} - -# installing Java through brew recommendation -export CPPFLAGS="-I$HB_PREFIX/opt/openjdk/include" - -# installing liquibase through brew recommendation -export LIQUIBASE_HOME=$(brew --prefix)/opt/liquibase/libexec - -export PATH="$HB_PREFIX/bin:$PATH:$HB_PREFIX/opt/openjdk/bin" -``` - -#### Node through node-version manager (nvm) - -We aren't using too many node-specific features, anything from 16 should work fine, -this will install the LTS version: - -```shell -brew install nvm - -# you may need to add the the following to your .zshrc -# export NVM_DIR="$HOME/.nvm" -# [ -s "$HB_PREFIX/opt/nvm/nvm.sh" ] && \. "$HB_PREFIX/opt/nvm/nvm.sh" # This loads nvm -# [ -s "$HB_PREFIX/opt/nvm/etc/bash_completion.d/nvm" ] && \. "$HB_PREFIX/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion - -# install latest version of node + npm -nvm install --lts -``` - -#### OpenAPI generator - -You'll need this to generate the Python and Typescript API. - -```shell -npm install @openapitools/openapi-generator-cli -g -openapi-generator-cli version-manager set 5.3.0 - -# put these in your .zshrc -export OPENAPI_COMMAND="npx @openapitools/openapi-generator-cli" -alias openapi-generator="npx @openapitools/openapi-generator-cli" -``` - -#### MariaDB install - -If you're planning to install MariaDB locally, brew is the easiest: - -```shell - -brew install mariadb@10.8 -# start mariadb on computer start -brew services start mariadb@10.8 - -# make mariadb command available on path -export PATH="$HB_PREFIX/opt/mariadb@10.8/bin:$PATH" -``` - -#### Your .zshrc file - -If you installed all the software through brew and npm -like this guide suggests, your `.zshrc` may look like this: - - -```shell -alias openapi-generator="npx @openapitools/openapi-generator-cli" - -# homebrew should export this on an M1 Mac -# the intel default is /usr/local -export HB_PREFIX=${HOMEBREW_PREFIX-/usr/local} - -# metamist -export SM_ENVIRONMENT=LOCAL # good default to have -export SM_DEV_DB_USER=sm_api # makes it easier to copy liquibase update command -export OPENAPI_COMMAND="npx @openapitools/openapi-generator-cli" - -export PATH="$HB_PREFIX/bin:$HB_PREFIX/opt/mariadb@10.8/bin:$PATH:$HB_PREFIX/opt/openjdk/bin" - -export CPPFLAGS="-I$HB_PREFIX/opt/openjdk/include" -export LIQUIBASE_HOME=$(brew --prefix)/opt/liquibase/libexec - -# node -export NVM_DIR="$HOME/.nvm" -[ -s "$HB_PREFIX/opt/nvm/nvm.sh" ] && \. "$HB_PREFIX/opt/nvm/nvm.sh" # This loads nvm -[ -s "$HB_PREFIX/opt/nvm/etc/bash_completion.d/nvm" ] && \. "$HB_PREFIX/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion -``` - -### Database setup - -These are the default values for the SM database connection. -Please alter them if you use any different values when setting up the database. - -```shell -export SM_DEV_DB_USER=root # this is the default, but we now recommend sm_api -export SM_DEV_DB_PASSWORD= # empty password -export SM_DEV_DB_HOST=127.0.0.1 -export SM_DEV_DB_PORT=3306 # default mariadb port -export SM_DEV_DB_NAME=sm_dev; -``` - -Create the database in MariaDB (by default, we call it `sm_dev`): - -> In newer installs of MariaDB, the root user is protected by default. - -We'll setup a user called `sm_api`, and setup permissions - -```shell -sudo mysql -u root --execute " - CREATE DATABASE sm_dev; - CREATE USER sm_api@'%'; - CREATE USER sm_api@localhost; - CREATE ROLE sm_api_role; - GRANT sm_api_role TO sm_api@'%'; - GRANT sm_api_role TO sm_api@localhost; - SET DEFAULT ROLE sm_api_role FOR sm_api@'%'; - SET DEFAULT ROLE sm_api_role FOR sm_api@localhost; - GRANT ALL PRIVILEGES ON sm_dev.* TO sm_api_role; -" -``` - -Then, before you run you'll need to export the varied: - -```shell -# also put this in your .zshrc -export SM_DEV_DB_USER=sm_api -``` - -Download the `mariadb-java-client` and create the schema using liquibase: - -```shell -pushd db/ -wget https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar -liquibase \ - --changeLogFile project.xml \ - --url jdbc:mariadb://localhost/sm_dev \ - --driver org.mariadb.jdbc.Driver \ - --classpath mariadb-java-client-3.0.3.jar \ - --username ${SM_DEV_DB_USER:-root} \ - update -popd -``` - -#### Using Maria DB docker image - -Pull mariadb image - -```bash -docker pull mariadb:10.8.3 -``` - -Run a mariadb container that will server your database. `-p 3307:3306` remaps the port to 3307 in case if you local MySQL is already using 3306 - -```bash -docker stop mysql-p3307 # stop and remove if the container already exists -docker rm mysql-p3307 -# run with an empty root password -docker run -p 3307:3306 --name mysql-p3307 -e MYSQL_ALLOW_EMPTY_PASSWORD=true -d mariadb:10.8.3 -``` - -```bash -mysql --host=127.0.0.1 --port=3307 -u root -e 'CREATE DATABASE sm_dev;' -mysql --host=127.0.0.1 --port=3307 -u root -e 'show databases;' -``` - -Go into the `db/` subdirectory, download the `mariadb-java-client` and create the schema using liquibase: - -```bash - -pushd db/ -wget https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar -liquibase \ - --changeLogFile project.xml \ - --url jdbc:mariadb://127.0.0.1:3307/sm_dev \ - --driver org.mariadb.jdbc.Driver \ - --classpath mariadb-java-client-3.0.3.jar \ - --username root \ - update -popd -``` - -Finally, make sure you configure the server (making use of the environment variables) to point it to your local Maria DB server - -```bash -export SM_DEV_DB_PORT=3307 -``` - -### Running the server - -You'll want to set the following environment variables (permanently) in your -local development environment. - -The `SM_LOCALONLY_DEFAULTUSER` environment variable along with `ALLOWALLACCESS` to allow access to a local metamist server without providing a bearer token. This will allow you to test the front-end components that access data. This happens automatically on the production instance through the Google identity-aware-proxy. - -```shell -export SM_ALLOWALLACCESS=1 -export SM_LOCALONLY_DEFAULTUSER=$(whoami) -``` - -```shell -# ensures the SWAGGER page points to your local: (localhost:8000/docs) -# and ensures if you use the PythonAPI, it also points to your local -export SM_ENVIRONMENT=LOCAL -# skips permission checks in your local environment -export SM_ALLOWALLACCESS=true -# uses your username as the "author" in requests -export SM_LOCALONLY_DEFAULTUSER=$(whoami) - -# probably need this - - -# start the server -python3 -m api.server -# OR -# uvicorn --port 8000 --host 0.0.0.0 api.server:app -``` - -#### Running + debugging in VSCode - -The following `launch.json` is a good base to debug the web server in VSCode: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Run API", - "type": "python", - "request": "launch", - "module": "api.server", - "justMyCode": false, - "env": { - "SM_ALLOWALLACCESS": "true", - "SM_LOCALONLY_DEFAULTUSER": "-local", - "SM_ENVIRONMENT": "local", - "SM_DEV_DB_USER": "sm_api", - } - } - ] -} -``` - -We could now place breakpoints on the sample route (ie: `api/routes/sample.py`), and debug requests as they come in. - -Then in VSCode under the _Run and Debug_ tab (⌘⇧D), you can "Run API": - -![Run API](resources/debug-api.png) - -#### Quickstart: Generate and install the python installable API - -Generating the installable APIs (Python + Typescript) involves running -the server, getting the `/openapi.json`, and running `openapi-generator`. - -The `regenerate_api.py` script does this in a few ways: - -1. Uses a running server on `localhost:8000` -2. Runs a docker container from the `SM_DOCKER` environment variable -3. Spins up the server itself - -Most of the time, you'll use 1 or 3: - -```bash -# this will start the api.server, so make sure you have the dependencies installed, -python regenerate_api.py \ - && pip install . -``` - -If you'd prefer to use the Docker approach (eg: on CI), this command -will build the docker container and supply it to regenerate_api.py. - -```bash -# SM_DOCKER is a known env variable to regenerate_api.py -export SM_DOCKER="cpg/metamist-server:dev" -docker build --build-arg SM_ENVIRONMENT=local -t $SM_DOCKER -f deploy/api/Dockerfile . -python regenerate_api.py -``` - -#### Generating example data - -> You'll need to generate the installable API before running this step - -You can run the `generate_data.py` script to generate some -random data to look at. - -```shell -export SM_ENVIRONMENT=local # important -python test/data/generate_data.py -``` - -#### Developing the UI - -```shell -# Ensure you have started sm locally on your computer already, then in another tab open the UI. -# This will automatically proxy request to the server. -cd web -npm install -npm run compile -npm start -``` - -This will start a web server using Vite, running on [localhost:5173](http://localhost:5173). - - -### OpenAPI and Swagger - -The Web API uses `apispec` with OpenAPI3 annotations on each route to describe interactions with the server. We can generate a swagger UI and an installable -python module based on these annotations. - -Some handy links: - -- [OpenAPI specification](https://swagger.io/specification/) -- [Describing parameters](https://swagger.io/docs/specification/describing-parameters/) -- [Describing request body](https://swagger.io/docs/specification/describing-request-body/) -- [Media types](https://swagger.io/docs/specification/media-types/) - -The web API exposes this schema in two ways: - -- Swagger UI: `http://localhost:8000/docs` - - You can use this to construct requests to the server - - Make sure you fill in the Bearer token (at the top right ) -- OpenAPI schema: `http://localhost:8000/schema.json` - - Returns a JSON with the full OpenAPI 3 compliant schema. - - You could put this into the [Swagger editor](https://editor.swagger.io/) to see the same "Swagger UI" that `/api/docs` exposes. - - We generate the metamist installable Python API based on this schema. - -## Deployment - -The CPG deploy is managed through Cloud Run on the Google Cloud Platform. -The deploy github action builds the container, and is deployed. - -Additionally you can access metamist through the identity-aware proxy (IAP), -which handles the authentication through OAuth, allowing you to access the -front-end. +This project is licensed under the MIT License. You can see it in the [LICENSE](LICENSE) file in the root directory of this source tree. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 000000000..89d35b38b --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,421 @@ +# Installation + +This document provides detailed instructions on how to install the project. Follow these steps to set up the project on your local system. + +## Prerequisites + +[Homebrew](https://brew.sh) is the simplest way to install system dependencies needed for this project. + +[Chocolatey](https://community.chocolatey.org/) is a good equivalent to Homebrew for package management in Windows. + +## System Requirements + + +- **Node/NPM** (recommend using [pnpm](https://pnpm.io/motivation) for this, but you can also use [nvm](https://github.com/nvm-sh/nvm)) +- **MariaDB** (using MariaDB in docker is also good) +- **Java** (for Liquibase / OpenAPI Generator) +- **Liquibase** +- **OpenAPI Generator** +- **pyenv** +- **wget** *(optional)* + +### Mac + +```bash +brew install pnpm # recommended over nvm +# OR +brew install nvm + +brew install java +brew install liquibase +brew install pyenv +brew install wget + +# skip if you wish to install via docker +brew install mariadb@10.8 + +``` + +### Windows + +Instructions for Windows should theoretically work but we have only tested this project to work on -nix systems. As such, we are unable to verify any discrepancies on Windows, and there could be slight variations in your setup. + +```bash +# Assuming you have Chocolatey +choco install pnpm # Recommended +# OR +choco install nvm + +choco install jdk8 +choco install liquibase +choco install pyenv-win +choco install wget + +# skip if you wish to install via docker +choco install mariadb --version=10.8.3 +``` + +## Installation Steps + +### Creating the environment + +- Python dependencies for the `metamist` API package are listed in `setup.py`. +- Additional dev requirements are listed in `requirements-dev.txt`. +- Packages for the sever-side code are listed in `requirements.txt`. + +We *STRONGLY* encourage the use of `pyenv` for managing Python versions. Debugging and the server will run on a minimum python version of 3.10. Refer to the [team-docs](https://github.com/populationgenomics/team-docs/blob/main/python.md) for more instructions on how to set this up. + +Use of a virtual environment to contain all requirements is highly recommended: + +```bash +virtualenv venv +source venv/bin/activate +pip install -r requirements.txt -r requirements-dev.txt + +# Installs metamist as a package +pip install --editable . +``` + +You will also need to set the following environment variables. Adjust the paths if you installed the dependencies using an alternate means: + +```bash +# homebrew should export this on an M1 Mac +# the intel default is /usr/local +export HB_PREFIX=${HOMEBREW_PREFIX-/usr/local} + +# installing Java through brew recommendation +export CPPFLAGS="-I$HB_PREFIX/opt/openjdk/include" +export PATH="$HB_PREFIX/bin:$PATH:$HB_PREFIX/opt/openjdk/bin" + +# installing liquibase through brew recommendation +export LIQUIBASE_HOME=$(brew --prefix)/opt/liquibase/libexec + +# mariadb +export PATH="$HB_PREFIX/opt/mariadb@10.8/bin:$PATH" + +# metamist config +export SM_ENVIRONMENT=LOCAL # good default to have +export SM_DEV_DB_USER=sm_api # makes it easier to copy liquibase update command +``` + +You can also add these to your shell config file e.g `.zshrc` or `.bashrc` for persistence to new sessions. + +#### PNPM/NVM Config + +Depending on your choice for using `pnpm` or `nvm` you will have to configure your shell for it. + +If you installed `pnpm`, you should have a similar snippet from the brew installation output: + +```shell +export PNPM_HOME="/Users/$(whoami)/Library/pnpm" +case ":$PATH:" in + *":$PNPM_HOME:"*) ;; + *) export PATH="$PNPM_HOME:$PATH" ;; +esac +``` + +Add this to your `.zshrc` to auto-load on next shell session. + +If you installed `nvm`, you will need to add lazy load since `nvm` has high load penalties. + +- For Oh-My-Zsh users, you can just add the `nvm` plugin to your `.zshrc` via these [instructions](https://github.com/ohmyzsh/ohmyzsh/blob/master/plugins/nvm/README.md) + +- If you do NOT have Oh-My-Zsh, you can use this [plugin](https://github.com/undg/zsh-nvm-lazy-load): + +```shell +git clone https://github.com/undg/zsh-nvm-lazy-load $ZSH/custom/plugins/zsh-nvm + +#Add this to your plugins variable in the `.zshrc` file and then source the file. +plugins=(... zsh-nvm-lazy-load) +``` + +Once set up, install the OpenAPI Generator: + +- For `pnpm`: + +```shell +# Install npm via pnpm +# This also activates the env for you, replace `use` with `add` to only install it. +pnpm env use --global lts +pnpm install @openapitools/openapi-generator-cli -g +``` + +Add this to your `.zshrc`: + +```shell +# openapi +export OPENAPI_COMMAND="pnpm dlx @openapitools/openapi-generator-cli" +alias openapi-generator="pnpm dlx @openapitools/openapi-generator-cli" +``` + +- For `nvm`: + +```shell +# Install npm via nvm +nvm install --lts +npm install @openapitools/openapi-generator-cli -g +``` + +Add this to your `.zshrc`: + +```shell +# openapi +export OPENAPI_COMMAND="npx @openapitools/openapi-generator-cli" +alias openapi-generator="npx @openapitools/openapi-generator-cli" +``` + +Finally, set the version: + +```shell +openapi-generator-cli version-manager set 5.3.0 +``` + +### Database Setup - Native Installation + +Set the following environment variables: + +```bash +export SM_DEV_DB_USER=sm_api +export SM_DEV_DB_PASSWORD= # empty password +export SM_DEV_DB_HOST=127.0.0.1 +export SM_DEV_DB_PORT=3306 # default mariadb port +export SM_DEV_DB_NAME=sm_dev; +``` + +Next, create the database `sm_dev` in MariaDB. + +> In newer versions of MariaDB, the root user is protected. + +Create a new user `sm_api` and provide permissions: + +```bash +sudo mysql -u root --execute " + CREATE DATABASE sm_dev; + CREATE USER sm_api@'%'; + CREATE USER sm_api@localhost; + CREATE ROLE sm_api_role; + GRANT sm_api_role TO sm_api@'%'; + GRANT sm_api_role TO sm_api@localhost; + SET DEFAULT ROLE sm_api_role FOR sm_api@'%'; + SET DEFAULT ROLE sm_api_role FOR sm_api@localhost; + GRANT ALL PRIVILEGES ON sm_dev.* TO sm_api_role; +" +``` + +Using `liquibase` we can now set up the tables as per the schema in `db/project.xml`: + +```bash +pushd db/ +wget https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar +liquibase \ + --changeLogFile project.xml \ + --url jdbc:mariadb://localhost/sm_dev \ + --driver org.mariadb.jdbc.Driver \ + --classpath mariadb-java-client-3.0.3.jar \ + --username ${SM_DEV_DB_USER:-root} \ + update +popd +``` + +### Database Setup - Docker Installation + +Ensure you have Docker installed or follow [this guide](https://docs.docker.com/engine/install/) to setup. + +Pull the image: + +```bash +docker pull mariadb:10.8.3 +``` + +Run the container on port 3306: + +```bash +docker run --name mariadb-p3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -p 3306:3306 -d docker.io/library/mariadb:10.8.3 +``` + +If you have a local MySQL instance already running on port 3306, you can map the docker container to run on 3307: + +```bash +docker run --name mariadb-p3307 -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -p 3307:3306 -d docker.io/library/mariadb:10.8.3 +``` + +You can now execute bash commands inside a shell: + +```bash +docker exec -it mariadb-p3306 bash +``` + +Set up the database with the `sm_api` user and appropriate permissions: + +```bash +mysql -u root --execute " + CREATE DATABASE sm_dev; + CREATE USER sm_api@'%'; + CREATE USER sm_api@localhost; + CREATE ROLE sm_api_role; + GRANT sm_api_role TO sm_api@'%'; + GRANT sm_api_role TO sm_api@localhost; + SET DEFAULT ROLE sm_api_role FOR sm_api@'%'; + SET DEFAULT ROLE sm_api_role FOR sm_api@localhost; + GRANT ALL PRIVILEGES ON sm_dev.* TO sm_api_role; +" +``` + +Exit the container bash shell once done and on the host, run liquibase with the correct port mapping to set up the tables: + +```bash +pushd db/ +wget https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/3.0.3/mariadb-java-client-3.0.3.jar +liquibase \ + --changeLogFile project.xml \ + --url jdbc:mariadb://127.0.0.1:3306/sm_dev \ + --driver org.mariadb.jdbc.Driver \ + --classpath mariadb-java-client-3.0.3.jar \ + --username root \ + update +popd +``` + +Ensure the database port environment variable matches the mapping above: + +```bash +export SM_DEV_DB_PORT=3306 # or 3307 +``` + +## Running the server + +You'll want to set the following environment variables (permanently) in your local development environment. + +The `SM_ENVIRONMENT`, `SM_LOCALONLY_DEFAULTUSER` and `SM_ALLOWALLACCESS` environment variables allow access to a local metamist server without providing a bearer token. + +This will allow you to test the front-end components that access data. This happens automatically on the production instance through the Google identity-aware-proxy. + +```bash +# ensures the SWAGGER page points to your local: (localhost:8000/docs) +# and ensures if you use the PythonAPI, it also points to your local +export SM_ENVIRONMENT=LOCAL +# skips permission checks in your local environment +export SM_ALLOWALLACCESS=true +# uses your username as the "author" in requests +export SM_LOCALONLY_DEFAULTUSER=$(whoami) +``` + +With those variables set, it is a good time to populate some test data if this is your first time running this server: + +```bash +python3 test/data/generate_data.py +``` + +You can now run the server: + +```bash +# start the server +python3 -m api.server +# OR +# uvicorn --port 8000 --host 0.0.0.0 api.server:app +``` + + +## Running Locally for Dev + +### Running and Debugging in VS Code + +The following `launch.json` is a good base to debug the web server in VS Code: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run API", + "type": "python", + "request": "launch", + "module": "api.server", + "justMyCode": false, + "env": { + "SM_ALLOWALLACCESS": "true", + "SM_LOCALONLY_DEFAULTUSER": "-local", + "SM_ENVIRONMENT": "local", + "SM_DEV_DB_USER": "sm_api", + } + } + ] +} +``` + +You can now place breakpoints anywhere and debug the API with "Run API" under the *Run and Debug* tab (⌘⇧D) or (Ctrl+Shift+D): + +![Run and Debug](../resources/debug-api.png) + +### Generate and install the python installable API + +After making any changes to the logic, it is worth regenerating the API with the OpenAPI Generator. + +Generating the installable APIs (Python + Typescript) involves running the server, getting the `/openapi.json`, and running `openapi-generator`. + +The `regenerate_api.py` script does this for us in a few ways: + +1. Uses a running server on `localhost:8000` +2. Runs a docker container from the `SM_DOCKER` environment variable. +3. Spins up the server itself. + +You can simply run: + +```bash +# this will start the api.server, so make sure you have the dependencies installed, +python regenerate_api.py \ + && pip install . +``` + +or if you prefer the Docker approach (eg: for CI), this command will build the docker container and supply it to `regenerate_api.py`: + +```bash +# SM_DOCKER is a known env variable to regenerate_api.py +export SM_DOCKER="cpg/metamist-server:dev" +docker build --build-arg SM_ENVIRONMENT=local -t $SM_DOCKER -f deploy/api/Dockerfile . +python regenerate_api.py +``` + +### Developing the UI + +```bash +# Ensure you have started metamist server locally on your computer already, then in another tab open the UI. +# This will automatically proxy request to the server. +cd web +npm install +npm run compile +npm start +``` + +This will start a web server using Vite, running on `localhost:5173`. + +### OpenAPI and Swagger + +The Web API uses `apispec` with OpenAPI3 annotations on each route to describe interactions with the server. We can generate a swagger UI and an installable +python module based on these annotations. + +Some handy links: + +- [OpenAPI specification](https://swagger.io/specification/) +- [Describing parameters](https://swagger.io/docs/specification/describing-parameters/) +- [Describing request body](https://swagger.io/docs/specification/describing-request-body/) +- [Media types](https://swagger.io/docs/specification/media-types/) + +The web API exposes this schema in two ways: + +- Swagger UI: `http://localhost:8000/docs` + - You can use this to construct requests to the server + - Make sure you fill in the Bearer token (at the top right ) +- OpenAPI schema: `http://localhost:8000/schema.json` + - Returns a JSON with the full OpenAPI 3 compliant schema. + - You could put this into the [Swagger editor](https://editor.swagger.io/) to see the same "Swagger UI" that `/api/docs` exposes. + - We generate the metamist installable Python API based on this schema. + +## Deployment + +The CPG deploy is managed through Cloud Run on the Google Cloud Platform. +The deploy github action builds the container, and is deployed. + +Additionally you can access metamist through the identity-aware proxy (IAP), +which handles the authentication through OAuth, allowing you to access the +front-end. diff --git a/resources/2024-01-15_db-diagram.png b/resources/2024-01-15_db-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..8a34e0032a4dc6d010ea27fb07b976bea28c85ff GIT binary patch literal 148500 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 Date: Wed, 7 Feb 2024 09:45:27 +1100 Subject: [PATCH 3/4] Add isort + sort files (#677) Co-authored-by: Michael Franklin --- .pre-commit-config.yaml | 18 ++++++++++++++++ .pylintrc | 2 +- api/graphql/filters.py | 2 +- api/routes/__init__.py | 12 +++++------ api/routes/assay.py | 10 ++------- api/routes/enum.py | 3 ++- api/routes/family.py | 8 +++---- api/routes/imports.py | 10 ++++----- api/routes/participant.py | 10 ++++----- api/routes/sample.py | 9 ++++---- api/routes/sequencing_groups.py | 8 +++---- api/utils/__init__.py | 5 +++-- api/utils/dates.py | 2 +- api/utils/extensions.py | 1 - db/backup/backup.py | 4 +--- db/backup/recovery_test.py | 8 +++---- db/backup/restore.py | 1 + db/python/connect.py | 12 ++++++----- db/python/gcp_connect.py | 1 + db/python/layers/assay.py | 4 ++-- db/python/layers/search.py | 2 +- db/python/layers/seqr.py | 1 + etl/extract/main.py | 4 ++-- etl/load/main.py | 5 +---- metamist/audit/audit_upload_bucket.py | 9 ++++---- metamist/audit/audithelper.py | 1 + .../audit/delete_assay_files_from_audit.py | 8 ++++--- metamist/audit/generic_auditor.py | 9 ++++---- metamist/graphql/__init__.py | 10 ++++----- metamist/parser/cloudhelper.py | 5 ++--- metamist/parser/generic_metadata_parser.py | 3 +-- metamist/parser/sample_file_map_parser.py | 5 +---- metamist_infrastructure/__init__.py | 1 + metamist_infrastructure/__main__.py | 1 + metamist_infrastructure/driver.py | 1 + metamist_infrastructure/slack_notification.py | 1 + models/models/sequencing_group.py | 1 - models/models/web.py | 2 +- models/utils/sample_id_format.py | 3 ++- models/utils/sequencing_group_id_format.py | 5 +++-- pyproject.toml | 21 +++++++++++++++++++ scripts/add_cram_size.py | 9 ++++---- scripts/check_md5s.py | 3 ++- scripts/check_md5s_from_metamist.py | 8 ++++--- scripts/check_sequence_files.py | 7 ++++--- scripts/create_md5s.py | 3 ++- scripts/create_test_subset.py | 6 +++--- scripts/etl_caller_example.py | 7 ++++--- scripts/fix_family_ids.py | 2 ++ scripts/fix_participant_ids.py | 2 ++ scripts/parse_participant_meta.py | 1 - scripts/process_ont_products.py | 5 +---- test/data/generate_seqr_project_data.py | 3 +-- test/test_assay.py | 3 +-- test/test_generate_data.py | 3 ++- test/test_generic_auditor.py | 4 ++-- test/test_get_participants.py | 1 + test/test_metamist.py | 1 + test/test_models.py | 2 +- test/test_parse_file_map.py | 2 +- test/test_sequencing_groups.py | 7 ++++--- test/test_update_participant_family.py | 5 +++-- test/test_upsert.py | 6 ++---- tob_metadata_harmonisation.py | 4 +--- 64 files changed, 180 insertions(+), 142 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 545ead223..3432209bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,24 @@ repos: - id: markdownlint args: ["--config", ".markdownlint.json"] + - repo: https://github.com/populationgenomics/pre-commits + rev: "v0.1.3" + hooks: + - id: cpg-id-checker + args: ["--extra-pattern", 'TOB\d+'] + exclude: >- + (?x)^( + test/test_generic_auditor\.py| + models/utils/sequencing_group_id_format\.py| + metamist/audit/README\.md + )$ + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + - repo: https://github.com/ambv/black rev: 23.12.1 hooks: diff --git a/.pylintrc b/.pylintrc index 773475a32..6ccbd8d1c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -23,7 +23,7 @@ disable=f-string-without-interpolation,inherit-non-class,too-few-public-methods, fixme,logging-fstring-interpolation,import-error,missing-module-docstring,line-too-long, too-many-return-statements,no-name-in-module,R0801,consider-using-set-comprehension, - arguments-differ,unspecified-encoding,invalid-name,logging-not-lazy,I1101 + arguments-differ,unspecified-encoding,invalid-name,logging-not-lazy,I1101,wrong-import-order # Overriding variable name patterns to allow short 1- or 2-letter variables attr-rgx=[a-z_][a-z0-9_]{0,30}$ diff --git a/api/graphql/filters.py b/api/graphql/filters.py index 68ae3a3e5..398db3a00 100644 --- a/api/graphql/filters.py +++ b/api/graphql/filters.py @@ -1,4 +1,4 @@ -from typing import TypeVar, Generic, Callable, Any +from typing import Any, Callable, Generic, TypeVar import strawberry diff --git a/api/routes/__init__.py b/api/routes/__init__.py index 18edb5969..748d37e2b 100644 --- a/api/routes/__init__.py +++ b/api/routes/__init__.py @@ -1,11 +1,11 @@ -from api.routes.sample import router as sample_router -from api.routes.imports import router as import_router from api.routes.analysis import router as analysis_router from api.routes.assay import router as assay_router -from api.routes.participant import router as participant_router +from api.routes.billing import router as billing_router +from api.routes.enum import router as enum_router from api.routes.family import router as family_router +from api.routes.imports import router as import_router +from api.routes.participant import router as participant_router from api.routes.project import router as project_router -from api.routes.web import router as web_router -from api.routes.enum import router as enum_router +from api.routes.sample import router as sample_router from api.routes.sequencing_groups import router as sequencing_groups_router -from api.routes.billing import router as billing_router +from api.routes.web import router as web_router diff --git a/api/routes/assay.py b/api/routes/assay.py index 3770d5a76..01ac57e77 100644 --- a/api/routes/assay.py +++ b/api/routes/assay.py @@ -3,20 +3,14 @@ from fastapi import APIRouter from api.utils import get_project_readonly_connection -from api.utils.db import ( - Connection, - get_projectless_db_connection, -) +from api.utils.db import Connection, get_projectless_db_connection +from db.python.layers.assay import AssayLayer from db.python.tables.assay import AssayFilter from db.python.tables.project import ProjectPermissionsTable -from db.python.layers.assay import ( - AssayLayer, -) from db.python.utils import GenericFilter from models.models.assay import AssayUpsert from models.utils.sample_id_format import sample_id_transform_to_raw_list - router = APIRouter(prefix='/assay', tags=['assay']) diff --git a/api/routes/enum.py b/api/routes/enum.py index c655ddca3..e61bf5e3a 100644 --- a/api/routes/enum.py +++ b/api/routes/enum.py @@ -1,7 +1,8 @@ -from typing import Type from inspect import isclass +from typing import Type from fastapi import APIRouter + from api.utils.db import get_projectless_db_connection from db.python import enum_tables from db.python.enum_tables.enums import EnumTable diff --git a/api/routes/family.py b/api/routes/family.py index 8ca615b42..38b5298f2 100644 --- a/api/routes/family.py +++ b/api/routes/family.py @@ -1,19 +1,19 @@ # pylint: disable=invalid-name -import io -import csv import codecs +import csv +import io from datetime import date from typing import List, Optional -from fastapi import APIRouter, UploadFile, File, Query +from fastapi import APIRouter, File, Query, UploadFile from pydantic import BaseModel from starlette.responses import StreamingResponse from api.utils import get_projectless_db_connection from api.utils.db import ( + Connection, get_project_readonly_connection, get_project_write_connection, - Connection, ) from api.utils.export import ExportType from api.utils.extensions import guess_delimiter_by_upload_file_obj diff --git a/api/routes/imports.py b/api/routes/imports.py index d119875de..7f8f84bf2 100644 --- a/api/routes/imports.py +++ b/api/routes/imports.py @@ -1,15 +1,15 @@ -import csv import codecs +import csv from typing import Optional -from fastapi import APIRouter, UploadFile, File +from fastapi import APIRouter, File, UploadFile +from api.utils.db import Connection, get_project_write_connection +from api.utils.extensions import guess_delimiter_by_upload_file_obj from db.python.layers.participant import ( - ParticipantLayer, ExtraParticipantImporterHandler, + ParticipantLayer, ) -from api.utils.extensions import guess_delimiter_by_upload_file_obj -from api.utils.db import get_project_write_connection, Connection router = APIRouter(prefix='/import', tags=['import']) diff --git a/api/routes/participant.py b/api/routes/participant.py index fc2678fb1..f0b5ca55a 100644 --- a/api/routes/participant.py +++ b/api/routes/participant.py @@ -4,18 +4,16 @@ from fastapi import APIRouter from fastapi.params import Query -from starlette.responses import StreamingResponse, JSONResponse +from starlette.responses import JSONResponse, StreamingResponse from api.utils import get_projectless_db_connection from api.utils.db import ( - get_project_write_connection, - get_project_readonly_connection, Connection, + get_project_readonly_connection, + get_project_write_connection, ) from api.utils.export import ExportType -from db.python.layers.participant import ( - ParticipantLayer, -) +from db.python.layers.participant import ParticipantLayer from models.models.participant import ParticipantUpsert from models.models.sample import sample_id_format diff --git a/api/routes/sample.py b/api/routes/sample.py index 646a2dc7b..338d81747 100644 --- a/api/routes/sample.py +++ b/api/routes/sample.py @@ -1,18 +1,17 @@ from fastapi import APIRouter, Body from api.utils.db import ( - get_project_write_connection, - get_project_readonly_connection, Connection, + get_project_readonly_connection, + get_project_write_connection, get_projectless_db_connection, ) from db.python.layers.sample import SampleLayer from db.python.tables.project import ProjectPermissionsTable from models.models.sample import SampleUpsert -from models.utils.sample_id_format import ( - # Sample, - sample_id_transform_to_raw, +from models.utils.sample_id_format import ( # Sample, sample_id_format, + sample_id_transform_to_raw, sample_id_transform_to_raw_list, ) diff --git a/api/routes/sequencing_groups.py b/api/routes/sequencing_groups.py index edfbe170d..f3be40236 100644 --- a/api/routes/sequencing_groups.py +++ b/api/routes/sequencing_groups.py @@ -1,18 +1,18 @@ from typing import Any + from fastapi import APIRouter from pydantic import BaseModel from api.utils.db import ( - get_project_readonly_connection, Connection, - get_projectless_db_connection, + get_project_readonly_connection, get_project_write_connection, + get_projectless_db_connection, ) from db.python.layers.sequencing_group import SequencingGroupLayer from models.models.sequencing_group import SequencingGroupUpsertInternal from models.utils.sample_id_format import sample_id_format -from models.utils.sequencing_group_id_format import ( - # Sample, +from models.utils.sequencing_group_id_format import ( # Sample, sequencing_group_id_format_list, sequencing_group_id_transform_to_raw, ) diff --git a/api/utils/__init__.py b/api/utils/__init__.py index 448a08d7f..fe5ae4cdd 100644 --- a/api/utils/__init__.py +++ b/api/utils/__init__.py @@ -1,13 +1,14 @@ """Importing GCP libraries""" from collections import defaultdict -from typing import TypeVar, Callable, Iterable -from .openapi import get_openapi_schema_func +from typing import Callable, Iterable, TypeVar + from .db import ( authenticate, get_project_readonly_connection, get_project_write_connection, get_projectless_db_connection, ) +from .openapi import get_openapi_schema_func T = TypeVar('T') X = TypeVar('X') diff --git a/api/utils/dates.py b/api/utils/dates.py index 2ef961f01..6c8c57796 100644 --- a/api/utils/dates.py +++ b/api/utils/dates.py @@ -1,4 +1,4 @@ -from datetime import datetime, date, timedelta +from datetime import date, datetime, timedelta INVOICE_DAY_DIFF = 3 diff --git a/api/utils/extensions.py b/api/utils/extensions.py index 6fefaa54d..b7a755a31 100644 --- a/api/utils/extensions.py +++ b/api/utils/extensions.py @@ -1,7 +1,6 @@ import csv from typing import Optional - EXTENSION_TO_DELIM_MAP = { '.csv': ',', '.tsv': '\t', diff --git a/db/backup/backup.py b/db/backup/backup.py index 261ac48e6..68cb6a9b6 100644 --- a/db/backup/backup.py +++ b/db/backup/backup.py @@ -8,9 +8,7 @@ from datetime import datetime from typing import Literal -from google.cloud import storage -from google.cloud import logging -from google.cloud import secretmanager +from google.cloud import logging, secretmanager, storage STORAGE_CLIENT = storage.Client() LOGGING_CLIENT = logging.Client() diff --git a/db/backup/recovery_test.py b/db/backup/recovery_test.py index 9a4616a1a..0c79fd7c1 100644 --- a/db/backup/recovery_test.py +++ b/db/backup/recovery_test.py @@ -5,18 +5,18 @@ """ -import unittest -import subprocess import json import os -from typing import Tuple, Optional +import subprocess +import unittest from collections import namedtuple +from typing import Optional, Tuple + import google.cloud.secretmanager import mysql.connector from parameterized import parameterized from restore import pull_latest_backup, restore - BACKUP_BUCKET = 'cpg-sm-backups' LOCAL_BACKUP_FOLDER = 'latest_backup' diff --git a/db/backup/restore.py b/db/backup/restore.py index 8a6a05cc9..f4c7c446c 100644 --- a/db/backup/restore.py +++ b/db/backup/restore.py @@ -3,6 +3,7 @@ import os import subprocess + from google.cloud import storage BACKUP_BUCKET = 'cpg-sm-backups' diff --git a/db/python/connect.py b/db/python/connect.py index 971058f8f..4a5abcba4 100644 --- a/db/python/connect.py +++ b/db/python/connect.py @@ -70,9 +70,11 @@ async def audit_log_id(self): async with self._audit_log_lock: if not self._audit_log_id: - # pylint: disable=import-outside-toplevel + # make this import here, otherwise we'd have a circular import - from db.python.tables.audit_log import AuditLogTable + from db.python.tables.audit_log import ( # pylint: disable=import-outside-toplevel,R0401 + AuditLogTable, + ) at = AuditLogTable(self) self._audit_log_id = await at.create_audit_log( @@ -154,9 +156,9 @@ def get_connection_string(self): if self.port: _host += f':{self.port}' - options: dict[ - str, str | int - ] = {} # {'min_size': self.min_pool_size, 'max_size': self.max_pool_size} + options: dict[str, str | int] = ( + {} + ) # {'min_size': self.min_pool_size, 'max_size': self.max_pool_size} _options = '&'.join(f'{k}={v}' for k, v in options.items()) url = f'mysql://{u_p}@{_host}/{self.dbname}?{_options}' diff --git a/db/python/gcp_connect.py b/db/python/gcp_connect.py index 649a0abc6..72fbc97df 100644 --- a/db/python/gcp_connect.py +++ b/db/python/gcp_connect.py @@ -6,6 +6,7 @@ import google.cloud.bigquery as bq from google.cloud import pubsub_v1 + from db.python.utils import InternalError logging.basicConfig(level=logging.DEBUG) diff --git a/db/python/layers/assay.py b/db/python/layers/assay.py index 0e512e6e7..3328acd82 100644 --- a/db/python/layers/assay.py +++ b/db/python/layers/assay.py @@ -1,10 +1,10 @@ # pylint: disable=too-many-arguments from typing import Any -from db.python.utils import NoOpAenter from db.python.layers.base import BaseLayer, Connection -from db.python.tables.assay import AssayTable, AssayFilter +from db.python.tables.assay import AssayFilter, AssayTable from db.python.tables.sample import SampleTable +from db.python.utils import NoOpAenter from models.models.assay import AssayInternal, AssayUpsertInternal diff --git a/db/python/layers/search.py b/db/python/layers/search.py index 3e94a7ca9..98187250d 100644 --- a/db/python/layers/search.py +++ b/db/python/layers/search.py @@ -1,13 +1,13 @@ import asyncio from typing import List, Optional -from db.python.utils import NotFoundError from db.python.layers.base import BaseLayer, Connection from db.python.tables.family import FamilyTable from db.python.tables.participant import ParticipantTable from db.python.tables.project import ProjectPermissionsTable from db.python.tables.sample import SampleTable from db.python.tables.sequencing_group import SequencingGroupTable +from db.python.utils import NotFoundError from models.enums.search import SearchResponseType from models.models.sample import sample_id_format, sample_id_transform_to_raw from models.models.search import ( diff --git a/db/python/layers/seqr.py b/db/python/layers/seqr.py index cad435852..b455a177c 100644 --- a/db/python/layers/seqr.py +++ b/db/python/layers/seqr.py @@ -12,6 +12,7 @@ import slack_sdk import slack_sdk.errors from cloudpathlib import AnyPath + from cpg_utils.cloud import get_google_identity_token from api.settings import ( diff --git a/etl/extract/main.py b/etl/extract/main.py index 6b05cbbaf..8e3971042 100644 --- a/etl/extract/main.py +++ b/etl/extract/main.py @@ -7,10 +7,10 @@ import flask import functions_framework import google.cloud.bigquery as bq -from cpg_utils.cloud import email_from_id_token - from google.cloud import pubsub_v1 # type: ignore +from cpg_utils.cloud import email_from_id_token + BIGQUERY_TABLE = os.getenv('BIGQUERY_TABLE') PUBSUB_TOPIC = os.getenv('PUBSUB_TOPIC') diff --git a/etl/load/main.py b/etl/load/main.py index b0a2a0a26..58d7ea5ce 100644 --- a/etl/load/main.py +++ b/etl/load/main.py @@ -9,11 +9,8 @@ import flask import functions_framework import google.cloud.bigquery as bq - -from google.cloud import pubsub_v1 # type: ignore - import pkg_resources - +from google.cloud import pubsub_v1 # type: ignore BIGQUERY_TABLE = os.getenv('BIGQUERY_TABLE') BIGQUERY_LOG_TABLE = os.getenv('BIGQUERY_LOG_TABLE') diff --git a/metamist/audit/audit_upload_bucket.py b/metamist/audit/audit_upload_bucket.py index 772fff409..22827a532 100644 --- a/metamist/audit/audit_upload_bucket.py +++ b/metamist/audit/audit_upload_bucket.py @@ -4,21 +4,20 @@ and sequencing groups that have no aligned CRAM. """ -from datetime import datetime -import sys +import asyncio import logging import os -import asyncio +import sys +from datetime import datetime from functools import cache + import click from cpg_utils.config import get_config - from metamist.audit.generic_auditor import GenericAuditor from metamist.graphql import gql, query - FASTQ_EXTENSIONS = ('.fq.gz', '.fastq.gz', '.fq', '.fastq') BAM_EXTENSIONS = ('.bam',) CRAM_EXTENSIONS = ('.cram',) diff --git a/metamist/audit/audithelper.py b/metamist/audit/audithelper.py index d20810ad9..7b58fb2a1 100644 --- a/metamist/audit/audithelper.py +++ b/metamist/audit/audithelper.py @@ -6,6 +6,7 @@ from typing import Any from cloudpathlib import AnyPath + from cpg_utils.cloud import get_path_components_from_gcp_path from metamist.parser.cloudhelper import CloudHelper diff --git a/metamist/audit/delete_assay_files_from_audit.py b/metamist/audit/delete_assay_files_from_audit.py index d3480d2c8..c60e42439 100644 --- a/metamist/audit/delete_assay_files_from_audit.py +++ b/metamist/audit/delete_assay_files_from_audit.py @@ -13,16 +13,18 @@ """ import csv -from datetime import datetime import logging import os import sys +from datetime import datetime + import click +from cloudpathlib import AnyPath, CloudPath from google.cloud import storage -from cloudpathlib import CloudPath, AnyPath + from cpg_utils.config import get_config -from metamist.audit.audithelper import AuditHelper +from metamist.audit.audithelper import AuditHelper CLIENT = storage.Client() diff --git a/metamist/audit/generic_auditor.py b/metamist/audit/generic_auditor.py index 032522fc6..c69eacf40 100644 --- a/metamist/audit/generic_auditor.py +++ b/metamist/audit/generic_auditor.py @@ -1,13 +1,14 @@ -from collections import defaultdict, namedtuple -from functools import cache import logging import os -from typing import Any +from collections import defaultdict, namedtuple from datetime import datetime +from functools import cache +from typing import Any from gql.transport.requests import log as requests_logger + from metamist.audit.audithelper import AuditHelper -from metamist.graphql import query_async, gql +from metamist.graphql import gql, query_async ANALYSIS_TYPES_QUERY = gql( """ diff --git a/metamist/graphql/__init__.py b/metamist/graphql/__init__.py index 2cd00b996..5696293f7 100644 --- a/metamist/graphql/__init__.py +++ b/metamist/graphql/__init__.py @@ -5,21 +5,21 @@ - validate queries with metamist schema (by fetching the schema) """ import os -from typing import Dict, Any +from typing import Any, Dict -from gql import Client, gql as gql_constructor +from gql import Client +from gql import gql as gql_constructor from gql.transport.aiohttp import AIOHTTPTransport from gql.transport.aiohttp import log as aiohttp_logger from gql.transport.requests import RequestsHTTPTransport from gql.transport.requests import log as requests_logger -from cpg_utils.cloud import get_google_identity_token - # this does not import itself, it imports the module from graphql import DocumentNode # type: ignore -import metamist.configuration +from cpg_utils.cloud import get_google_identity_token +import metamist.configuration _sync_client: Client | None = None _async_client: Client | None = None diff --git a/metamist/parser/cloudhelper.py b/metamist/parser/cloudhelper.py index c4f03aa4d..a03c89b20 100644 --- a/metamist/parser/cloudhelper.py +++ b/metamist/parser/cloudhelper.py @@ -1,14 +1,13 @@ # pylint: disable=no-member -import os import logging -from typing import Iterable, Callable, TypeVar +import os from collections import defaultdict from datetime import datetime +from typing import Callable, Iterable, TypeVar from cloudpathlib import AnyPath, GSPath from google.cloud import storage - # type declarations for GroupBy T = TypeVar('T') X = TypeVar('X') diff --git a/metamist/parser/generic_metadata_parser.py b/metamist/parser/generic_metadata_parser.py index ac3145249..d89104df0 100644 --- a/metamist/parser/generic_metadata_parser.py +++ b/metamist/parser/generic_metadata_parser.py @@ -8,13 +8,12 @@ import click -from metamist.parser.generic_parser import ( +from metamist.parser.generic_parser import ( # noqa GenericParser, GroupedRow, ParsedAnalysis, ParsedAssay, ParsedSequencingGroup, - # noqa SingleRow, run_as_sync, ) diff --git a/metamist/parser/sample_file_map_parser.py b/metamist/parser/sample_file_map_parser.py index 20f8eb5b9..077aca912 100644 --- a/metamist/parser/sample_file_map_parser.py +++ b/metamist/parser/sample_file_map_parser.py @@ -5,10 +5,7 @@ import click -from metamist.parser.generic_metadata_parser import ( - GenericMetadataParser, - run_as_sync, -) +from metamist.parser.generic_metadata_parser import GenericMetadataParser, run_as_sync from metamist.parser.generic_parser import SingleRow PARTICIPANT_COL_NAME = 'individual_id' diff --git a/metamist_infrastructure/__init__.py b/metamist_infrastructure/__init__.py index 9007ac9a6..420050dd9 100644 --- a/metamist_infrastructure/__init__.py +++ b/metamist_infrastructure/__init__.py @@ -3,5 +3,6 @@ """ import sys + if 'unittest' not in sys.modules: from metamist_infrastructure.driver import MetamistInfrastructure diff --git a/metamist_infrastructure/__main__.py b/metamist_infrastructure/__main__.py index 7cc77df3d..efdf0c08d 100644 --- a/metamist_infrastructure/__main__.py +++ b/metamist_infrastructure/__main__.py @@ -9,6 +9,7 @@ from typing import NamedTuple from cpg_infra.config import CPGInfrastructureConfig + from metamist_infrastructure import MetamistInfrastructure GCP_PROJECT = os.getenv('METAMIST_INFRA_GCP_PROJECT') diff --git a/metamist_infrastructure/driver.py b/metamist_infrastructure/driver.py index 96ef95207..8e69d2577 100644 --- a/metamist_infrastructure/driver.py +++ b/metamist_infrastructure/driver.py @@ -9,6 +9,7 @@ import pulumi import pulumi_gcp as gcp + from cpg_infra.plugin import CpgInfrastructurePlugin from cpg_infra.utils import archive_folder diff --git a/metamist_infrastructure/slack_notification.py b/metamist_infrastructure/slack_notification.py index d0fb33ebb..89a9ab1ec 100644 --- a/metamist_infrastructure/slack_notification.py +++ b/metamist_infrastructure/slack_notification.py @@ -9,6 +9,7 @@ import pulumi import pulumi_gcp as gcp + from cpg_infra.utils import archive_folder from cpg_utils.cloud import read_secret diff --git a/models/models/sequencing_group.py b/models/models/sequencing_group.py index 1ccd1a991..4f4ed878e 100644 --- a/models/models/sequencing_group.py +++ b/models/models/sequencing_group.py @@ -8,7 +8,6 @@ sequencing_group_id_transform_to_raw, ) - SequencingGroupInternalId = int SequencingGroupExternalId = str diff --git a/models/models/web.py b/models/models/web.py index d368f29ab..0c96ed90f 100644 --- a/models/models/web.py +++ b/models/models/web.py @@ -2,7 +2,7 @@ import dataclasses from models.base import SMBase -from models.models.participant import NestedParticipantInternal, NestedParticipant +from models.models.participant import NestedParticipant, NestedParticipantInternal class WebProject(SMBase): diff --git a/models/utils/sample_id_format.py b/models/utils/sample_id_format.py index 31bd873eb..154ecb410 100644 --- a/models/utils/sample_id_format.py +++ b/models/utils/sample_id_format.py @@ -1,5 +1,6 @@ from typing import Iterable -from api.settings import SAMPLE_PREFIX, SAMPLE_CHECKSUM_OFFSET + +from api.settings import SAMPLE_CHECKSUM_OFFSET, SAMPLE_PREFIX from models.utils.luhn import luhn_compute, luhn_is_valid diff --git a/models/utils/sequencing_group_id_format.py b/models/utils/sequencing_group_id_format.py index 98a374b63..5be3e164d 100644 --- a/models/utils/sequencing_group_id_format.py +++ b/models/utils/sequencing_group_id_format.py @@ -1,6 +1,7 @@ from typing import Iterable -from api.settings import SEQUENCING_GROUP_PREFIX, SEQUENCING_GROUP_CHECKSUM_OFFSET -from models.utils.luhn import luhn_is_valid, luhn_compute + +from api.settings import SEQUENCING_GROUP_CHECKSUM_OFFSET, SEQUENCING_GROUP_PREFIX +from models.utils.luhn import luhn_compute, luhn_is_valid def sequencing_group_id_transform_to_raw_list( diff --git a/pyproject.toml b/pyproject.toml index a9d613157..d8e889718 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,3 +3,24 @@ line-length = 88 skip-string-normalization = true exclude = "metamist/" include = "metamist/parser/" + +[tool.isort] +py_version = 311 +profile = "black" +line_length = 88 +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "HAIL", "CPG", "FIRSTPARTY", "LOCALFOLDER"] +known_hail = [ + "hail", + "hailtop", +] +# Adjust these for each repository, e.g., removing those that should be +# local rather than CPG. Also fill in extend_skip below if there are any +# subdirectories that should be ignored. +known_cpg = [ + "analysis_runner", + "cpg_infra", + "cpg_utils", + "cpg_workflows", + "gnomad", + "hail_scripts", +] diff --git a/scripts/add_cram_size.py b/scripts/add_cram_size.py index 1872960c4..1e00b04ef 100644 --- a/scripts/add_cram_size.py +++ b/scripts/add_cram_size.py @@ -3,22 +3,21 @@ This script goes through all CRAMS in sample-metadata, gets the size, and updates the meta['size'] attribute on the analysis. """ -import re +import asyncio import logging import os -import asyncio +import re from typing import Dict, List -from google.cloud import storage from google.api_core.exceptions import NotFound +from google.cloud import storage from api.utils import group_by - from metamist.apis import AnalysisApi, ProjectApi from metamist.model.analysis_query_model import AnalysisQueryModel -from metamist.model.analysis_update_model import AnalysisUpdateModel from metamist.model.analysis_status import AnalysisStatus from metamist.model.analysis_type import AnalysisType +from metamist.model.analysis_update_model import AnalysisUpdateModel from metamist.parser.generic_parser import chunk logger = logging.getLogger(__name__) diff --git a/scripts/check_md5s.py b/scripts/check_md5s.py index 75104c722..1bea22388 100644 --- a/scripts/check_md5s.py +++ b/scripts/check_md5s.py @@ -2,9 +2,10 @@ from typing import Set import click -import hailtop.batch as hb from google.cloud import storage +import hailtop.batch as hb + DRIVER_IMAGE = 'australia-southeast1-docker.pkg.dev/analysis-runner/images/driver:8cc869505251c8396fefef01c42225a7b7930a97-hail-0.2.73.devc6f6f09cec08' diff --git a/scripts/check_md5s_from_metamist.py b/scripts/check_md5s_from_metamist.py index 0fa39546e..a57a1ee2b 100644 --- a/scripts/check_md5s_from_metamist.py +++ b/scripts/check_md5s_from_metamist.py @@ -1,14 +1,16 @@ -from collections import namedtuple import math import os +from collections import namedtuple from shlex import quote import click + import hailtop.batch as hb -from cpg_utils.hail_batch import remote_tmpdir + from cpg_utils.config import get_config +from cpg_utils.hail_batch import remote_tmpdir -from metamist.apis import SampleApi, AssayApi +from metamist.apis import AssayApi, SampleApi LocationTuple = namedtuple( 'LocationTuple', ['cpg_sample_id', 'location', 'checksum', 'size'] diff --git a/scripts/check_sequence_files.py b/scripts/check_sequence_files.py index 318945c22..e8c22414f 100755 --- a/scripts/check_sequence_files.py +++ b/scripts/check_sequence_files.py @@ -4,14 +4,15 @@ Find sequencing files that exist in the bucket, but are not ingested. This pairs well will the cleanup_fastqs.py script. """ -import os import asyncio import logging +import os from collections import defaultdict -from typing import Set, Dict, Any, List +from typing import Any, Dict, List, Set from google.cloud import storage -from metamist.apis import WebApi, SampleApi, AssayApi, ProjectApi + +from metamist.apis import AssayApi, ProjectApi, SampleApi, WebApi # Global vars logger = logging.getLogger(__file__) diff --git a/scripts/create_md5s.py b/scripts/create_md5s.py index f99f64906..fe7c78ae5 100644 --- a/scripts/create_md5s.py +++ b/scripts/create_md5s.py @@ -1,9 +1,10 @@ import os import click -from cpg_utils.hail_batch import get_batch, get_config, copy_common_env from google.cloud import storage +from cpg_utils.hail_batch import copy_common_env, get_batch, get_config + def create_md5s_for_files_in_directory(skip_filetypes: tuple[str, str], force_recreate: bool, gs_dir): """Validate files with MD5s in the provided gs directory""" diff --git a/scripts/create_test_subset.py b/scripts/create_test_subset.py index 14f8ccd00..448d89b54 100755 --- a/scripts/create_test_subset.py +++ b/scripts/create_test_subset.py @@ -21,14 +21,14 @@ from google.cloud import storage -from metamist.apis import AnalysisApi, AssayApi, SampleApi, FamilyApi, ParticipantApi +from metamist.apis import AnalysisApi, AssayApi, FamilyApi, ParticipantApi, SampleApi from metamist.graphql import gql, query from metamist.models import ( - AssayUpsert, - SampleUpsert, Analysis, AnalysisStatus, AnalysisUpdateModel, + AssayUpsert, + SampleUpsert, SequencingGroupUpsert, ) diff --git a/scripts/etl_caller_example.py b/scripts/etl_caller_example.py index 32544c942..8f353bd91 100644 --- a/scripts/etl_caller_example.py +++ b/scripts/etl_caller_example.py @@ -7,11 +7,12 @@ pip install requests google-auth requests urllib3 """ import os -import requests -import google.oauth2.id_token + import google.auth.transport.requests -from urllib3 import Retry +import google.oauth2.id_token +import requests from requests.adapters import HTTPAdapter +from urllib3 import Retry URL = 'https://metamist-etl-mnrpw3mdza-ts.a.run.app' TYPE = 'NAME_OF_EXTERNAL_PARTY/v1' diff --git a/scripts/fix_family_ids.py b/scripts/fix_family_ids.py index 8357deda5..c714916cd 100644 --- a/scripts/fix_family_ids.py +++ b/scripts/fix_family_ids.py @@ -2,7 +2,9 @@ Small script to update external family IDs """ import json + import click + from metamist.apis import FamilyApi from metamist.model.family_update_model import FamilyUpdateModel diff --git a/scripts/fix_participant_ids.py b/scripts/fix_participant_ids.py index 80990649b..f8ed64dcb 100644 --- a/scripts/fix_participant_ids.py +++ b/scripts/fix_participant_ids.py @@ -2,7 +2,9 @@ Small script to update external participant IDs """ import json + import click + from metamist.apis import ParticipantApi diff --git a/scripts/parse_participant_meta.py b/scripts/parse_participant_meta.py index dac68a01e..a351842f6 100644 --- a/scripts/parse_participant_meta.py +++ b/scripts/parse_participant_meta.py @@ -12,7 +12,6 @@ import traceback import click - from google.cloud import storage from metamist import ApiException diff --git a/scripts/process_ont_products.py b/scripts/process_ont_products.py index d5ba354a0..b160d8696 100644 --- a/scripts/process_ont_products.py +++ b/scripts/process_ont_products.py @@ -11,10 +11,7 @@ from metamist.model.analysis import Analysis from metamist.model.analysis_status import AnalysisStatus from metamist.parser.cloudhelper import CloudHelper -from metamist.parser.generic_metadata_parser import ( - GenericMetadataParser, - run_as_sync, -) +from metamist.parser.generic_metadata_parser import GenericMetadataParser, run_as_sync from metamist.parser.generic_parser import CustomDictReader, chunk OntAnalysesPreparer = namedtuple( diff --git a/test/data/generate_seqr_project_data.py b/test/data/generate_seqr_project_data.py index ec3424217..5a3f7f5bf 100644 --- a/test/data/generate_seqr_project_data.py +++ b/test/data/generate_seqr_project_data.py @@ -7,9 +7,8 @@ import random import sys import tempfile - from pprint import pprint -from typing import Set, List +from typing import List, Set from metamist.apis import AnalysisApi, FamilyApi, ParticipantApi, ProjectApi, SampleApi from metamist.graphql import gql, query_async diff --git a/test/test_assay.py b/test/test_assay.py index d53e79eb8..6c26d26ad 100644 --- a/test/test_assay.py +++ b/test/test_assay.py @@ -2,12 +2,11 @@ from pymysql.err import IntegrityError -from db.python.utils import NotFoundError from db.python.enum_tables import AssayTypeTable from db.python.layers.assay import AssayLayer from db.python.layers.sample import SampleLayer from db.python.tables.assay import AssayFilter -from db.python.utils import GenericFilter +from db.python.utils import GenericFilter, NotFoundError from models.models.assay import AssayUpsertInternal from models.models.sample import SampleUpsertInternal diff --git a/test/test_generate_data.py b/test/test_generate_data.py index 56be80269..de7995c1d 100644 --- a/test/test_generate_data.py +++ b/test/test_generate_data.py @@ -1,7 +1,8 @@ import unittest from test.data.generate_data import QUERY_ENUMS, QUERY_SG_ID -from metamist.graphql import validate, configure_sync_client + from api.graphql.schema import schema # type: ignore +from metamist.graphql import configure_sync_client, validate class ValidateGenerateDataQueries(unittest.TestCase): diff --git a/test/test_generic_auditor.py b/test/test_generic_auditor.py index 69cb6a324..491c97d05 100644 --- a/test/test_generic_auditor.py +++ b/test/test_generic_auditor.py @@ -1,3 +1,5 @@ +# noqa: B006 + import unittest import unittest.mock from collections import namedtuple @@ -5,8 +7,6 @@ from metamist.audit.generic_auditor import GenericAuditor -# noqa: B006 - class TestGenericAuditor(unittest.TestCase): """Test the audit helper functions""" diff --git a/test/test_get_participants.py b/test/test_get_participants.py index 195470165..48680611f 100644 --- a/test/test_get_participants.py +++ b/test/test_get_participants.py @@ -1,4 +1,5 @@ from test.testbase import DbIsolatedTest, run_as_sync + from db.python.layers.participant import ParticipantLayer from models.models.participant import ParticipantUpsertInternal diff --git a/test/test_metamist.py b/test/test_metamist.py index aeb6cab5f..809a5b8ca 100644 --- a/test/test_metamist.py +++ b/test/test_metamist.py @@ -1,4 +1,5 @@ import unittest + from metamist.models import AssayUpsert diff --git a/test/test_models.py b/test/test_models.py index 061754b1f..2f395a5c8 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -3,8 +3,8 @@ from models.models import ( ParticipantUpsert, ParticipantUpsertInternal, - SampleUpsertInternal, SampleUpsert, + SampleUpsertInternal, ) from models.utils.sample_id_format import sample_id_format diff --git a/test/test_parse_file_map.py b/test/test_parse_file_map.py index 6b4ecea59..22e7b700f 100644 --- a/test/test_parse_file_map.py +++ b/test/test_parse_file_map.py @@ -1,6 +1,6 @@ from io import StringIO -from unittest.mock import patch from test.testbase import DbIsolatedTest, run_as_sync +from unittest.mock import patch from metamist.parser.generic_parser import ParsedParticipant from metamist.parser.sample_file_map_parser import SampleFileMapParser diff --git a/test/test_sequencing_groups.py b/test/test_sequencing_groups.py index d1006b98b..3139f13c0 100644 --- a/test/test_sequencing_groups.py +++ b/test/test_sequencing_groups.py @@ -1,11 +1,12 @@ from test.testbase import DbIsolatedTest, run_as_sync -from db.python.utils import GenericFilter + +from db.python.layers import SampleLayer, SequencingGroupLayer from db.python.tables.sequencing_group import SequencingGroupFilter -from db.python.layers import SequencingGroupLayer, SampleLayer +from db.python.utils import GenericFilter from models.models import ( - SequencingGroupUpsertInternal, AssayUpsertInternal, SampleUpsertInternal, + SequencingGroupUpsertInternal, ) diff --git a/test/test_update_participant_family.py b/test/test_update_participant_family.py index 23315e222..e8517dd91 100644 --- a/test/test_update_participant_family.py +++ b/test/test_update_participant_family.py @@ -1,9 +1,10 @@ from test.testbase import DbIsolatedTest, run_as_sync + from pymysql.err import IntegrityError -from models.models import ParticipantUpsertInternal -from db.python.layers.participant import ParticipantLayer from db.python.layers.family import FamilyLayer +from db.python.layers.participant import ParticipantLayer +from models.models import ParticipantUpsertInternal class TestParticipantFamily(DbIsolatedTest): diff --git a/test/test_upsert.py b/test/test_upsert.py index 0e4c1e78a..02d46a378 100644 --- a/test/test_upsert.py +++ b/test/test_upsert.py @@ -1,12 +1,10 @@ from test.testbase import DbIsolatedTest, run_as_sync -from db.python.layers.participant import ( - ParticipantLayer, -) +from db.python.layers.participant import ParticipantLayer +from models.models.assay import AssayUpsertInternal from models.models.participant import ParticipantUpsertInternal from models.models.sample import SampleUpsertInternal from models.models.sequencing_group import SequencingGroupUpsertInternal -from models.models.assay import AssayUpsertInternal default_assay_meta = { 'sequencing_type': 'genome', diff --git a/tob_metadata_harmonisation.py b/tob_metadata_harmonisation.py index 1008719af..4a259297e 100644 --- a/tob_metadata_harmonisation.py +++ b/tob_metadata_harmonisation.py @@ -9,12 +9,10 @@ """ from typing import Dict -from metamist.graphql import gql, query - from metamist.apis import AssayApi +from metamist.graphql import gql, query from metamist.models import AssayUpsert - SG_QUERY = gql( """ query MyQuery($active_status: Boolean!) { From 5debebf1b9ecfd48ef2e1e6084f489447ed06ef1 Mon Sep 17 00:00:00 2001 From: EddieLF <34049565+EddieLF@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:31:50 +1100 Subject: [PATCH 4/4] Fix pid sgid map api (#680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix participant to sg_id api bug * Remove sample id format import * Add sequencing type option to endpoint * Bump version: 6.6.2 → 6.7.0 --- .bumpversion.cfg | 2 +- api/routes/participant.py | 24 ++++++++++++++---------- api/server.py | 2 +- deploy/python/version.txt | 2 +- setup.py | 2 +- web/package.json | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9ae63ecc9..6331c36cc 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 6.6.2 +current_version = 6.7.0 commit = True tag = False parse = (?P\d+)\.(?P\d+)\.(?P[A-z0-9-]+) diff --git a/api/routes/participant.py b/api/routes/participant.py index f0b5ca55a..61b3db382 100644 --- a/api/routes/participant.py +++ b/api/routes/participant.py @@ -15,7 +15,7 @@ from api.utils.export import ExportType from db.python.layers.participant import ParticipantLayer from models.models.participant import ParticipantUpsert -from models.models.sample import sample_id_format +from models.models.sequencing_group import sequencing_group_id_format router = APIRouter(prefix='/participant', tags=['participant']) @@ -111,35 +111,37 @@ async def update_many_participant_external_ids( @router.get( - '/{project}/external-pid-to-internal-sample-id', - operation_id='getExternalParticipantIdToInternalSampleId', + '/{project}/external-pid-to-sg-id', + operation_id='getExternalParticipantIdToSequencingGroupId', tags=['seqr'], ) -async def get_external_participant_id_to_internal_sample_id( +async def get_external_participant_id_to_sequencing_group_id( project: str, + sequencing_type: str = None, export_type: ExportType = ExportType.JSON, flip_columns: bool = False, connection: Connection = get_project_readonly_connection, ): """ - Get csv / tsv export of external_participant_id to internal_sample_id + Get csv / tsv export of external_participant_id to sequencing_group_id - Get a map of {external_participant_id} -> {internal_sample_id} - useful to matching joint-called samples in the matrix table to the participant + Get a map of {external_participant_id} -> {sequencing_group_id} + useful to matching joint-called sequencing groups in the matrix table to the participant Return a list not dictionary, because dict could lose participants with multiple samples. + :param sequencing_type: Leave empty to get all sequencing types :param flip_columns: Set to True when exporting for seqr """ player = ParticipantLayer(connection) # this wants project ID (connection.project) assert connection.project m = await player.get_external_participant_id_to_internal_sequencing_group_id_map( - project=connection.project + project=connection.project, sequencing_type=sequencing_type ) - rows = [[pid, sample_id_format(sid)] for pid, sid in m] + rows = [[pid, sequencing_group_id_format(sgid)] for pid, sgid in m] if flip_columns: rows = [r[::-1] for r in rows] @@ -151,7 +153,9 @@ async def get_external_participant_id_to_internal_sample_id( writer.writerows(rows) ext = export_type.get_extension() - filename = f'{project}-participant-to-sample-map-{date.today().isoformat()}{ext}' + filename = f'{project}-participant-to-sequencing-group-map-{date.today().isoformat()}{ext}' + if sequencing_type: + filename = f'{project}-{sequencing_type}-participant-to-sequencing-group-map-{date.today().isoformat()}{ext}' return StreamingResponse( # stream the whole file at once, because it's all in memory anyway iter([output.getvalue()]), diff --git a/api/server.py b/api/server.py index 652aa4322..1879bec50 100644 --- a/api/server.py +++ b/api/server.py @@ -19,7 +19,7 @@ from db.python.utils import get_logger # This tag is automatically updated by bump2version -_VERSION = '6.6.2' +_VERSION = '6.7.0' logger = get_logger() diff --git a/deploy/python/version.txt b/deploy/python/version.txt index 28179fc1f..f0e13c509 100644 --- a/deploy/python/version.txt +++ b/deploy/python/version.txt @@ -1 +1 @@ -6.6.2 +6.7.0 diff --git a/setup.py b/setup.py index 0b812b931..1c1d579c0 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( name=PKG, # This tag is automatically updated by bump2version - version='6.6.2', + version='6.7.0', description='Python API for interacting with the Sample API system', long_description=readme, long_description_content_type='text/markdown', diff --git a/web/package.json b/web/package.json index 69ffc33f6..40e260617 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "metamist", - "version": "6.6.2", + "version": "6.7.0", "private": true, "dependencies": { "@apollo/client": "^3.7.3",