From bceb3ad0696044ac57ca8fc292938d3eea28b924 Mon Sep 17 00:00:00 2001 From: asherpasha Date: Tue, 29 Aug 2023 20:28:29 -0400 Subject: [PATCH 1/7] Updated dependancies. --- .github/workflows/bar-api.yml | 2 +- Dockerfile | 2 +- docker-compose.yml | 4 ++-- docs/requirements.txt | 40 +++++++++++++++++------------------ requirements.txt | 32 ++++++++++++++-------------- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/bar-api.yml b/.github/workflows/bar-api.yml index ca61222..6cc09d1 100644 --- a/.github/workflows/bar-api.yml +++ b/.github/workflows/bar-api.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.8, 3.9, 3.10.11, 3.11] + python-version: [3.8, 3.9, 3.10.13, 3.11] services: redis: diff --git a/Dockerfile b/Dockerfile index 5b3b310..3612126 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.3-bullseye +FROM python:3.11.5-bookworm WORKDIR /usr/src/app diff --git a/docker-compose.yml b/docker-compose.yml index 13b894c..394398f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.7" services: mysqldb: - image: mysql:8.0.33 + image: mysql:8.0.34 container_name: BAR_mysqldb # Must use this for mariadb client to connect command: --default-authentication-plugin=mysql_native_password @@ -12,7 +12,7 @@ services: - MYSQL_ROOT_PASSWORD=root redis: - image: redis:7.0.11 + image: redis:7.2.0 container_name: BAR_redis restart: always ports: diff --git a/docs/requirements.txt b/docs/requirements.txt index dcf41b4..91f7292 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,27 +1,27 @@ alabaster==0.7.13 -Babel==2.11.0 -beautifulsoup4==4.11.1 -certifi==2022.12.7 -charset-normalizer==3.0.1 -docutils==0.19 -furo==2022.12.7 +Babel==2.12.1 +beautifulsoup4==4.12.2 +certifi==2023.7.22 +charset-normalizer==3.2.0 +docutils==0.20.1 +furo==2023.8.19 idna==3.4 imagesize==1.4.1 Jinja2==3.1.2 -MarkupSafe==2.1.2 -packaging==23.0 -Pygments==2.14.0 -pytz==2022.7.1 -requests==2.28.2 +MarkupSafe==2.1.3 +packaging==23.1 +Pygments==2.16.1 +pytz==2023.3 +requests==2.31.0 snowballstemmer==2.2.0 -soupsieve==2.3.2.post1 -Sphinx==5.3.0 +soupsieve==2.4.1 +Sphinx==7.2.4 sphinx-basic-ng==1.0.0b1 -sphinx-copybutton==0.5.1 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.0 +sphinx-copybutton==0.5.2 +sphinxcontrib-applehelp==1.0.7 +sphinxcontrib-devhelp==1.0.5 +sphinxcontrib-htmlhelp==2.0.4 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 -urllib3==1.26.14 +sphinxcontrib-qthelp==1.0.6 +sphinxcontrib-serializinghtml==1.1.9 +urllib3==2.0.4 diff --git a/requirements.txt b/requirements.txt index 4a1b36a..de7a458 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,29 +1,29 @@ aniso8601==9.0.1 -async-timeout==4.0.2 +async-timeout==4.0.3 attrs==23.1.0 black==23.7.0 blinker==1.6.2 cachelib==0.9.0 certifi==2023.7.22 charset-normalizer==3.2.0 -click==8.1.6 -coverage==7.2.7 +click==8.1.7 +coverage==7.3.0 Deprecated==1.2.14 flake8==6.1.0 -Flask==2.3.2 +Flask==2.3.3 Flask-Caching==2.0.2 Flask-Cors==4.0.0 -Flask-Limiter==3.3.1 +Flask-Limiter==3.4.1 flask-marshmallow==0.15.0 flask-restx==1.1.0 Flask-SQLAlchemy==3.0.5 greenlet==2.0.2 idna==3.4 -importlib-resources==6.0.0 +importlib-resources==6.0.1 iniconfig==2.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 -jsonschema==4.18.4 +jsonschema==4.19.0 jsonschema-specifications==2023.7.1 limits==3.5.0 markdown-it-py==3.0.0 @@ -32,27 +32,27 @@ marshmallow==3.20.1 mccabe==0.7.0 mdurl==0.1.2 mypy-extensions==1.0.0 -mysqlclient==2.1.1 +mysqlclient==2.2.0 ordered-set==4.1.0 packaging==23.1 pathspec==0.11.2 platformdirs==3.10.0 -pluggy==1.2.0 +pluggy==1.3.0 pycodestyle==2.11.0 pyflakes==3.1.0 -Pygments==2.15.1 +Pygments==2.16.1 pyrsistent==0.19.3 pytest==7.4.0 python-dateutil==2.8.2 pytz==2023.3 -redis==4.6.0 -referencing==0.30.0 +redis==5.0.0 +referencing==0.30.2 requests==2.31.0 -rich==13.5.1 -rpds-py==0.9.2 +rich==13.5.2 +rpds-py==0.10.0 six==1.16.0 -SQLAlchemy==2.0.19 +SQLAlchemy==2.0.20 typing_extensions==4.7.1 urllib3==2.0.4 -Werkzeug==2.3.6 +Werkzeug==2.3.7 wrapt==1.15.0 From aa04c378eef504afc08c306aaa422701fea493a7 Mon Sep 17 00:00:00 2001 From: Vin Date: Tue, 19 Sep 2023 13:39:07 -0400 Subject: [PATCH 2/7] Added FastPheno endpoint: - Band Endpoint grabs all band values for a given site, band and tree - Genotype_Id Endpoint grabs all trees for a given genotype - Uses updated SQL alchemy v2 syntax with mapped_columns - Includes tests --- api/__init__.py | 2 + api/models/fastpheno.py | 127 +++++++++++++++++++++++++ api/resources/fastpheno.py | 98 +++++++++++++++++++ config/BAR_API.cfg | 3 +- config/databases/fastpheno.sql | 153 ++++++++++++++++++++++++++++++ config/init.sh | 1 + tests/resources/test_fastpheno.py | 100 +++++++++++++++++++ 7 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 api/models/fastpheno.py create mode 100644 api/resources/fastpheno.py create mode 100644 config/databases/fastpheno.sql create mode 100644 tests/resources/test_fastpheno.py diff --git a/api/__init__.py b/api/__init__.py index 2d03910..ac8ed22 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -61,6 +61,7 @@ def create_app(): from api.resources.interactions import itrns from api.resources.gene_localizations import loc from api.resources.efp_image import efp_image + from api.resources.fastpheno import fastpheno bar_api.add_namespace(gene_information) bar_api.add_namespace(rnaseq_gene_expression) @@ -72,6 +73,7 @@ def create_app(): bar_api.add_namespace(itrns) bar_api.add_namespace(loc) bar_api.add_namespace(efp_image) + bar_api.add_namespace(fastpheno) bar_api.init_app(bar_app) return bar_app diff --git a/api/models/fastpheno.py b/api/models/fastpheno.py new file mode 100644 index 0000000..d308e63 --- /dev/null +++ b/api/models/fastpheno.py @@ -0,0 +1,127 @@ +from api import db +import enum +from sqlalchemy.dialects.mysql import DECIMAL, ENUM +from sqlalchemy.orm import relationship +from sqlalchemy import ForeignKey +from typing import List + + +class Sites(db.Model): + __bind_key__ = "fastpheno" + __tablename__ = "sites" + + sites_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) + site_name: db.Mapped[str] = db.mapped_column(db.String(45), nullable=False) + site_desc: db.Mapped[str] = db.mapped_column(db.String(99), nullable=True) + children: db.Mapped[List["Trees"]] = relationship() + + +class Trees(db.Model): + __bind_key__ = "fastpheno" + __tablename__ = "trees" + + trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) + sites_pk: db.Mapped[int] = db.mapped_column(ForeignKey("sites.sites_pk")) + longitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) + latitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) + genotype_id: db.Mapped[str] = db.mapped_column(db.String(5), nullable=True) + external_link: db.Mapped[str] = db.mapped_column(db.String(200), nullable=True) + tree_given_id: db.Mapped[str] = db.mapped_column(db.String(25), nullable=True) + children: db.Mapped[List["Band"]] = relationship() + + +class MonthChoices(enum.Enum): + jan = '1' + feb = '2' + mar = '3' + apr = '4' + may = '5' + jun = '6' + jul = '7' + aug = '8' + sep = '9' + oct = '10' + nov = '11' + dec = '12' + + +class Band(db.Model): + __bind_key__ = "fastpheno" + __tablename__ = "band" + + trees_pk: db.Mapped[int] = db.mapped_column(ForeignKey("trees.trees_pk"), primary_key=True) + month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) + band: db.Mapped[float] = db.mapped_column(db.String(100), nullable=False, primary_key=True) + value: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) + + +class Height(db.Model): + __bind_key__ = "fastpheno" + __tablename__ = "height" + + trees_pk: db.Mapped[int] = db.mapped_column(ForeignKey("trees.trees_pk"), primary_key=True) + month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) + tree_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) + ground_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) + + +# from api import db +# import enum +# from sqlalchemy.dialects.mysql import DECIMAL, ENUM + + +# class Sites(db.Model): +# __bind_key__ = "fastpheno" +# __tablename__ = "sites" + +# sites_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) +# site_name: db.Mapped[str] = db.mapped_column(db.String(45), nullable=False) +# site_desc: db.Mapped[str] = db.mapped_column(db.String(99), nullable=True) + + +# class Trees(db.Model): +# __bind_key__ = "fastpheno" +# __tablename__ = "trees" + +# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) +# sites_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False) +# longitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) +# latitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) +# genotype_id: db.Mapped[str] = db.mapped_column(db.String(5), nullable=True) +# external_link: db.Mapped[str] = db.mapped_column(db.String(200), nullable=True) +# tree_given_id: db.Mapped[str] = db.mapped_column(db.String(25), nullable=True) + + +# class MonthChoices(enum.Enum): +# jan = '1' +# feb = '2' +# mar = '3' +# apr = '4' +# may = '5' +# jun = '6' +# jul = '7' +# aug = '8' +# sep = '9' +# oct = '10' +# nov = '11' +# dec = '12' + + +# class Band(db.Model): +# __bind_key__ = "fastpheno" +# __tablename__ = "band" + +# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) +# month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) +# band: db.Mapped[float] = db.mapped_column(db.String(100), nullable=False) +# value: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) + + +# class Height(db.Model): +# __bind_key__ = "fastpheno" +# __tablename__ = "height" + +# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) +# month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) +# tree_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) +# ground_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) diff --git a/api/resources/fastpheno.py b/api/resources/fastpheno.py new file mode 100644 index 0000000..f80357f --- /dev/null +++ b/api/resources/fastpheno.py @@ -0,0 +1,98 @@ +""" +Date: Aug 2023 +Author: Vince L +Fastpheno endpoint for retrieving tree data +""" +from flask_restx import Namespace, Resource +from api import db +from api.models.fastpheno import Sites, Trees, Band, Height +from api.utils.bar_utils import BARUtils +from markupsafe import escape +import sys + + +fastpheno = Namespace("FastPheno", description="FastPheno API service", path="/fastpheno") + + +@fastpheno.route("/get_bands///") +class FastPheno(Resource): + @fastpheno.param("site", _in="path", default="pintendre") + @fastpheno.param("month", _in="path", default="jan") + @fastpheno.param("band", _in="path", default="band_1") + def get(self, site, month, band): + """This end point returns band values for a specific site AND month-date""" + # Escape input data + site = escape(site).capitalize() + month = escape(month) + band = escape(band) + + rows = db.session.execute( + db.select(Sites, Trees, Height, Band) + .select_from(Sites) + .join(Trees, Trees.sites_pk == Sites.sites_pk) # don't need to use 2nd arg, for clarity... I set ORM rel + .join(Height, Height.trees_pk == Trees.trees_pk) + .join(Band, Band.trees_pk == Trees.trees_pk) + .where( + Sites.site_name == site, Band.month == month, Height.month == month, Band.band == band + ) + ).all() + res = [ + { + "site_name" : s.site_name, + "tree_id" : t.trees_pk, + "longitude": t.longitude, + "latitutde": t.latitude, + "genotype_id": t.genotype_id, + "tree_given_id": t.tree_given_id, + "external_link": t.external_link, + "band_value" : float(b.value), + "tree_height_proxy" : float(h.tree_height_proxy), + "ground_height_proxy" : float(h.ground_height_proxy), + "band_month": b.month.name, + } + for s, t, h, b in rows + ] + if len(rows) == 0: + return ( + BARUtils.error_exit("There are no data found for the given parameters"), + 400, + ) + + return BARUtils.success_exit(res) + + +@fastpheno.route("/get_trees/") +class FastPhenoTrees(Resource): + @fastpheno.param("genotype_id", _in="path", default="C") + def get(self, genotype_id): + """This end point returns trees for a given genotype_id across sites""" + # Escape input data + genotype_id = escape(genotype_id).capitalize() + + rows = db.session.execute( + db.select(Sites, Trees) + .select_from(Sites) + .join(Trees, Trees.sites_pk == Sites.sites_pk) + .where( + Trees.genotype_id == genotype_id + ) + ).all() + res = [ + { + "site_name" : s.site_name, + "tree_id" : t.trees_pk, + "longitude": t.longitude, + "latitutde": t.latitude, + "genotype_id": t.genotype_id, + "tree_given_id": t.tree_given_id, + "external_link": t.external_link, + } + for s, t in rows + ] + if len(rows) == 0: + return ( + BARUtils.error_exit("There are no data found for the given parameters"), + 400, + ) + + return BARUtils.success_exit(res) diff --git a/config/BAR_API.cfg b/config/BAR_API.cfg index be3be4a..b714dd9 100644 --- a/config/BAR_API.cfg +++ b/config/BAR_API.cfg @@ -37,5 +37,6 @@ SQLALCHEMY_BINDS = { 'striga' : 'mysql://root:root@localhost/striga', 'tomato_nssnp' : 'mysql://root:root@localhost/tomato_nssnp', 'tomato_sequence' : 'mysql://root:root@localhost/tomato_sequence', - 'triphysaria' : 'mysql://root:root@localhost/triphysaria' + 'triphysaria' : 'mysql://root:root@localhost/triphysaria', + 'fastpheno' : 'mysql://root:root@localhost/fastpheno' } diff --git a/config/databases/fastpheno.sql b/config/databases/fastpheno.sql new file mode 100644 index 0000000..7f19979 --- /dev/null +++ b/config/databases/fastpheno.sql @@ -0,0 +1,153 @@ +-- MySQL dump 10.13 Distrib 8.0.33, for Linux (x86_64) +-- +-- Host: localhost Database: cannabis +-- ------------------------------------------------------ +-- Server version 8.0.33 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Current Database: `fastpheno` +-- + +DROP DATABASE IF EXISTS `fastpheno` ; + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `fastpheno` /*!40100 DEFAULT CHARACTER SET latin1 */ /*!80016 DEFAULT ENCRYPTION='N' */; + +USE `fastpheno`; + +-- +-- Table structure for table `sites` +-- + +DROP TABLE IF EXISTS `sites` ; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE IF NOT EXISTS `sites` ( + `sites_pk` INT NOT NULL AUTO_INCREMENT, + `site_name` VARCHAR(45) UNIQUE NOT NULL, + `site_desc` VARCHAR(999) NULL, + PRIMARY KEY (`sites_pk`)) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sites` +-- + +LOCK TABLES `sites` WRITE; +/*!40000 ALTER TABLE `sites` DISABLE KEYS */; +INSERT INTO `sites` VALUES (1,'Pintendre', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'),(2,'Pickering','Lorem ipsum dolor sit amet,'); +/*!40000 ALTER TABLE `sites` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `trees` +-- + +DROP TABLE IF EXISTS `trees` ; + +CREATE TABLE IF NOT EXISTS `trees` ( + `trees_pk` INT NOT NULL AUTO_INCREMENT, + `sites_pk` INT NOT NULL, + `longitude` DECIMAL(10) NOT NULL, + `latitude` DECIMAL(10) NOT NULL, + `genotype_id` VARCHAR(5) NULL, + `external_link` VARCHAR(200) NULL, + `tree_given_id` VARCHAR(25) NULL, + PRIMARY KEY (`trees_pk`), + INDEX `sites_fk_idx` (`sites_pk` ASC), + CONSTRAINT `sites_fk` + FOREIGN KEY (`sites_pk`) + REFERENCES `sites` (`sites_pk`) + ON DELETE RESTRICT + ON UPDATE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `trees` +-- + +LOCK TABLES `trees` WRITE; +/*!40000 ALTER TABLE `trees` DISABLE KEYS */; +INSERT INTO `trees` VALUES (1,1,336839,5178557,'C','example','11'),(2,1,336872,5178486,'C','example2','11'),(3,1,346872,5278486,'C','example3','B'),(4,2,330502,5262486,'XZ','example4','K123'); +/*!40000 ALTER TABLE `trees` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `band` +-- + +DROP TABLE IF EXISTS `band` ; + +CREATE TABLE IF NOT EXISTS `band` ( + `trees_pk` INT NOT NULL, + `month` ENUM('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec') NOT NULL, + `band` VARCHAR(100) NOT NULL, + `value` DECIMAL(20,15) NOT NULL, + INDEX `trees_fk_idx` (`trees_pk` ASC), + PRIMARY KEY (`trees_pk`, `month`, `band`), + CONSTRAINT `trees_fk` + FOREIGN KEY (`trees_pk`) + REFERENCES `trees` (`trees_pk`) + ON DELETE RESTRICT + ON UPDATE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `band` +-- + +LOCK TABLES `band` WRITE; +/*!40000 ALTER TABLE `band` DISABLE KEYS */; +INSERT INTO `band` VALUES (1,'jan','band_1',0.025796278000000),(1,'jan','band_2',0.025796278000000),(1,'feb','band_1',0.025796278000000),(1,'mar','band_1',0.0234423232241),(1,'apr','band_1',0.089900613000000),(2,'feb','band_1',0.183586478000000),(4,'feb','band_1',0.223586478000000); +/*!40000 ALTER TABLE `band` ENABLE KEYS */; +UNLOCK TABLES; + + +-- ----------------------------------------------------- +-- Table `height` +-- ----------------------------------------------------- +DROP TABLE IF EXISTS `height` ; + +CREATE TABLE IF NOT EXISTS `height` ( + `trees_pk` INT NOT NULL, + `month` ENUM('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec') NOT NULL, + `tree_height_proxy` DECIMAL(20,15) NOT NULL, + `ground_height_proxy` DECIMAL(20,15) NOT NULL, + INDEX `tree_fk_idx` (`trees_pk` ASC), + PRIMARY KEY (`trees_pk`, `month`), + CONSTRAINT `tree_fk` + FOREIGN KEY (`trees_pk`) + REFERENCES `trees` (`trees_pk`) + ON DELETE RESTRICT + ON UPDATE RESTRICT) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `height` +-- + +LOCK TABLES `height` WRITE; +/*!40000 ALTER TABLE `height` DISABLE KEYS */; +INSERT INTO `height` VALUES (1,'jan',2.23428942871000000,45.106719970000000),(1,'feb',3.478942871000000,49.106719970000000),(1,'mar',2.383630037000000,48.887859340000000),(1,'apr',1.376412749000000,49.052417760000000),(2,'feb',2.383630037000000,48.12341242131163),(4,'feb',2.623630037000000,45.22341242131163); +/*!40000 ALTER TABLE `height` ENABLE KEYS */; +UNLOCK TABLES; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2023-06-07 14:37:50 diff --git a/config/init.sh b/config/init.sh index a31c577..1e9f812 100755 --- a/config/init.sh +++ b/config/init.sh @@ -38,6 +38,7 @@ mysql -u $DB_USER -p$DB_PASS < ./config/databases/summarization.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_nssnp.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_sequence.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/triphysaria.sql +mysql -u $DB_USER -p$DB_PASS < ./config/databases/fastpheno.sql echo "Data are now loaded. Preparing API config" diff --git a/tests/resources/test_fastpheno.py b/tests/resources/test_fastpheno.py new file mode 100644 index 0000000..d35bdea --- /dev/null +++ b/tests/resources/test_fastpheno.py @@ -0,0 +1,100 @@ +from api import app +from unittest import TestCase + + +class TestIntegrations(TestCase): + def setUp(self): + self.app_client = app.test_client() + + def test_bands(self): + """This function checks GET request for fastpheno bands + :return: + """ + response = self.app_client.get("/fastpheno/get_bands/pintendre/feb/band_1") + expected = { + "wasSuccessful": True, + "data": [ + { + "site_name": "Pintendre", + "tree_id": 1, + "longitude": 336839, + "latitutde": 5178557, + "genotype_id": "C", + "tree_given_id": "11", + "external_link": "example", + "band_value": 0.025796278, + "tree_height_proxy": 3.478942871, + "ground_height_proxy": 49.10671997, + "band_month": "feb" + }, + { + "site_name": "Pintendre", + "tree_id": 2, + "longitude": 336872, + "latitutde": 5178486, + "genotype_id": "C", + "tree_given_id": "11", + "external_link": "example2", + "band_value": 0.183586478, + "tree_height_proxy": 2.383630037, + "ground_height_proxy": 48.12341242131163, + "band_month": "feb" + } + ] + } + self.assertEqual(response.json, expected) + + # Not working version + response = self.app_client.get("/fastpheno/get_bands/NOTASITE/feb/band_1") + expected = { + "wasSuccessful": False, + "error": "There are no data found for the given parameters", + } + self.assertEqual(response.json, expected) + + def test_site_genotype_ids(self): + """This function checks GET request for fastpheno sites for genotype_ids + :return: + """ + response = self.app_client.get("/fastpheno/get_trees/C") + expected = { + "wasSuccessful": True, + "data": [ + { + "site_name": "Pintendre", + "tree_id": 1, + "longitude": 336839, + "latitutde": 5178557, + "genotype_id": "C", + "tree_given_id": "11", + "external_link": "example" + }, + { + "site_name": "Pintendre", + "tree_id": 2, + "longitude": 336872, + "latitutde": 5178486, + "genotype_id": "C", + "tree_given_id": "11", + "external_link": "example2" + }, + { + "site_name": "Pintendre", + "tree_id": 3, + "longitude": 346872, + "latitutde": 5278486, + "genotype_id": "C", + "tree_given_id": "B", + "external_link": "example3" + } + ] + } + self.assertEqual(response.json, expected) + + # Not working version + response = self.app_client.get("/fastpheno/get_trees/NOTAGENOTYPE") + expected = { + "wasSuccessful": False, + "error": "There are no data found for the given parameters", + } + self.assertEqual(response.json, expected) From 30101acd84c21dc78196c59978d78578624d6584 Mon Sep 17 00:00:00 2001 From: Vin Date: Tue, 19 Sep 2023 14:50:20 -0400 Subject: [PATCH 3/7] - Minor flake8 issues for FastPheno endpoints --- api/models/fastpheno.py | 64 +------------------------------------- api/resources/fastpheno.py | 3 +- 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/api/models/fastpheno.py b/api/models/fastpheno.py index d308e63..8456970 100644 --- a/api/models/fastpheno.py +++ b/api/models/fastpheno.py @@ -48,7 +48,7 @@ class MonthChoices(enum.Enum): class Band(db.Model): __bind_key__ = "fastpheno" __tablename__ = "band" - + trees_pk: db.Mapped[int] = db.mapped_column(ForeignKey("trees.trees_pk"), primary_key=True) month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) band: db.Mapped[float] = db.mapped_column(db.String(100), nullable=False, primary_key=True) @@ -63,65 +63,3 @@ class Height(db.Model): month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) tree_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) ground_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) - - -# from api import db -# import enum -# from sqlalchemy.dialects.mysql import DECIMAL, ENUM - - -# class Sites(db.Model): -# __bind_key__ = "fastpheno" -# __tablename__ = "sites" - -# sites_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) -# site_name: db.Mapped[str] = db.mapped_column(db.String(45), nullable=False) -# site_desc: db.Mapped[str] = db.mapped_column(db.String(99), nullable=True) - - -# class Trees(db.Model): -# __bind_key__ = "fastpheno" -# __tablename__ = "trees" - -# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) -# sites_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False) -# longitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) -# latitude: db.Mapped[float] = db.mapped_column(db.Float, nullable=False) -# genotype_id: db.Mapped[str] = db.mapped_column(db.String(5), nullable=True) -# external_link: db.Mapped[str] = db.mapped_column(db.String(200), nullable=True) -# tree_given_id: db.Mapped[str] = db.mapped_column(db.String(25), nullable=True) - - -# class MonthChoices(enum.Enum): -# jan = '1' -# feb = '2' -# mar = '3' -# apr = '4' -# may = '5' -# jun = '6' -# jul = '7' -# aug = '8' -# sep = '9' -# oct = '10' -# nov = '11' -# dec = '12' - - -# class Band(db.Model): -# __bind_key__ = "fastpheno" -# __tablename__ = "band" - -# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) -# month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) -# band: db.Mapped[float] = db.mapped_column(db.String(100), nullable=False) -# value: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) - - -# class Height(db.Model): -# __bind_key__ = "fastpheno" -# __tablename__ = "height" - -# trees_pk: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, primary_key=True) -# month: db.Mapped[str] = db.mapped_column(ENUM(MonthChoices), nullable=False, primary_key=True) -# tree_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) -# ground_height_proxy: db.Mapped[float] = db.mapped_column(DECIMAL(20, 15), nullable=False) diff --git a/api/resources/fastpheno.py b/api/resources/fastpheno.py index f80357f..e2c2275 100644 --- a/api/resources/fastpheno.py +++ b/api/resources/fastpheno.py @@ -8,7 +8,6 @@ from api.models.fastpheno import Sites, Trees, Band, Height from api.utils.bar_utils import BARUtils from markupsafe import escape -import sys fastpheno = Namespace("FastPheno", description="FastPheno API service", path="/fastpheno") @@ -29,7 +28,7 @@ def get(self, site, month, band): rows = db.session.execute( db.select(Sites, Trees, Height, Band) .select_from(Sites) - .join(Trees, Trees.sites_pk == Sites.sites_pk) # don't need to use 2nd arg, for clarity... I set ORM rel + .join(Trees, Trees.sites_pk == Sites.sites_pk) # don't need to use 2nd arg, for clarity... I set ORM rel .join(Height, Height.trees_pk == Trees.trees_pk) .join(Band, Band.trees_pk == Trees.trees_pk) .where( From 9efd7e4d8e702fbeb7e8ff6934c06f72555c6c1a Mon Sep 17 00:00:00 2001 From: asherpasha Date: Wed, 20 Sep 2023 13:30:53 -0400 Subject: [PATCH 4/7] Fast Pheno ready to go live. Dep updated. --- api/models/fastpheno.py | 24 ++++++++++++------------ api/resources/fastpheno.py | 22 +++++++++------------- config/BAR_API.cfg | 4 ++-- config/init.sh | 2 +- requirements.txt | 26 +++++++++++++------------- tests/resources/test_fastpheno.py | 18 +++++++++--------- 6 files changed, 46 insertions(+), 50 deletions(-) diff --git a/api/models/fastpheno.py b/api/models/fastpheno.py index 8456970..abaa1a3 100644 --- a/api/models/fastpheno.py +++ b/api/models/fastpheno.py @@ -31,18 +31,18 @@ class Trees(db.Model): class MonthChoices(enum.Enum): - jan = '1' - feb = '2' - mar = '3' - apr = '4' - may = '5' - jun = '6' - jul = '7' - aug = '8' - sep = '9' - oct = '10' - nov = '11' - dec = '12' + jan = "1" + feb = "2" + mar = "3" + apr = "4" + may = "5" + jun = "6" + jul = "7" + aug = "8" + sep = "9" + oct = "10" + nov = "11" + dec = "12" class Band(db.Model): diff --git a/api/resources/fastpheno.py b/api/resources/fastpheno.py index e2c2275..81701f5 100644 --- a/api/resources/fastpheno.py +++ b/api/resources/fastpheno.py @@ -31,22 +31,20 @@ def get(self, site, month, band): .join(Trees, Trees.sites_pk == Sites.sites_pk) # don't need to use 2nd arg, for clarity... I set ORM rel .join(Height, Height.trees_pk == Trees.trees_pk) .join(Band, Band.trees_pk == Trees.trees_pk) - .where( - Sites.site_name == site, Band.month == month, Height.month == month, Band.band == band - ) + .where(Sites.site_name == site, Band.month == month, Height.month == month, Band.band == band) ).all() res = [ { - "site_name" : s.site_name, - "tree_id" : t.trees_pk, + "site_name": s.site_name, + "tree_id": t.trees_pk, "longitude": t.longitude, "latitutde": t.latitude, "genotype_id": t.genotype_id, "tree_given_id": t.tree_given_id, "external_link": t.external_link, - "band_value" : float(b.value), - "tree_height_proxy" : float(h.tree_height_proxy), - "ground_height_proxy" : float(h.ground_height_proxy), + "band_value": float(b.value), + "tree_height_proxy": float(h.tree_height_proxy), + "ground_height_proxy": float(h.ground_height_proxy), "band_month": b.month.name, } for s, t, h, b in rows @@ -72,14 +70,12 @@ def get(self, genotype_id): db.select(Sites, Trees) .select_from(Sites) .join(Trees, Trees.sites_pk == Sites.sites_pk) - .where( - Trees.genotype_id == genotype_id - ) + .where(Trees.genotype_id == genotype_id) ).all() res = [ { - "site_name" : s.site_name, - "tree_id" : t.trees_pk, + "site_name": s.site_name, + "tree_id": t.trees_pk, "longitude": t.longitude, "latitutde": t.latitude, "genotype_id": t.genotype_id, diff --git a/config/BAR_API.cfg b/config/BAR_API.cfg index b714dd9..2ad4b35 100644 --- a/config/BAR_API.cfg +++ b/config/BAR_API.cfg @@ -21,6 +21,7 @@ SQLALCHEMY_BINDS = { 'eplant_rice' : 'mysql://root:root@localhost/eplant_rice', 'eplant_soybean' : 'mysql://root:root@localhost/eplant_soybean', 'eplant_tomato' : 'mysql://root:root@localhost/eplant_tomato', + 'fastpheno' : 'mysql://root:root@localhost/fastpheno', 'germination': 'mysql://root:root@localhost/germination', 'kalanchoe': 'mysql://root:root@localhost/kalanchoe', 'klepikova': 'mysql://root:root@localhost/klepikova', @@ -37,6 +38,5 @@ SQLALCHEMY_BINDS = { 'striga' : 'mysql://root:root@localhost/striga', 'tomato_nssnp' : 'mysql://root:root@localhost/tomato_nssnp', 'tomato_sequence' : 'mysql://root:root@localhost/tomato_sequence', - 'triphysaria' : 'mysql://root:root@localhost/triphysaria', - 'fastpheno' : 'mysql://root:root@localhost/fastpheno' + 'triphysaria' : 'mysql://root:root@localhost/triphysaria' } diff --git a/config/init.sh b/config/init.sh index 1e9f812..4386705 100755 --- a/config/init.sh +++ b/config/init.sh @@ -20,6 +20,7 @@ mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_poplar.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_rice.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_soybean.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_tomato.sql +mysql -u $DB_USER -p$DB_PASS < ./config/databases/fastpheno.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/germination.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/kalanchoe.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/klepikova.sql @@ -38,7 +39,6 @@ mysql -u $DB_USER -p$DB_PASS < ./config/databases/summarization.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_nssnp.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_sequence.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/triphysaria.sql -mysql -u $DB_USER -p$DB_PASS < ./config/databases/fastpheno.sql echo "Data are now loaded. Preparing API config" diff --git a/requirements.txt b/requirements.txt index de7a458..eee9b47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,31 +1,31 @@ aniso8601==9.0.1 async-timeout==4.0.3 attrs==23.1.0 -black==23.7.0 +black==23.9.1 blinker==1.6.2 cachelib==0.9.0 certifi==2023.7.22 charset-normalizer==3.2.0 click==8.1.7 -coverage==7.3.0 +coverage==7.3.1 Deprecated==1.2.14 flake8==6.1.0 Flask==2.3.3 Flask-Caching==2.0.2 Flask-Cors==4.0.0 -Flask-Limiter==3.4.1 +Flask-Limiter==3.5.0 flask-marshmallow==0.15.0 flask-restx==1.1.0 -Flask-SQLAlchemy==3.0.5 +Flask-SQLAlchemy==3.1.1 greenlet==2.0.2 idna==3.4 importlib-resources==6.0.1 iniconfig==2.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 -jsonschema==4.19.0 +jsonschema==4.19.1 jsonschema-specifications==2023.7.1 -limits==3.5.0 +limits==3.6.0 markdown-it-py==3.0.0 MarkupSafe==2.1.3 marshmallow==3.20.1 @@ -42,17 +42,17 @@ pycodestyle==2.11.0 pyflakes==3.1.0 Pygments==2.16.1 pyrsistent==0.19.3 -pytest==7.4.0 +pytest==7.4.2 python-dateutil==2.8.2 -pytz==2023.3 +pytz==2023.3.post1 redis==5.0.0 referencing==0.30.2 requests==2.31.0 -rich==13.5.2 -rpds-py==0.10.0 +rich==13.5.3 +rpds-py==0.10.3 six==1.16.0 -SQLAlchemy==2.0.20 -typing_extensions==4.7.1 -urllib3==2.0.4 +SQLAlchemy==2.0.21 +typing_extensions==4.8.0 +urllib3==2.0.5 Werkzeug==2.3.7 wrapt==1.15.0 diff --git a/tests/resources/test_fastpheno.py b/tests/resources/test_fastpheno.py index d35bdea..85a23c1 100644 --- a/tests/resources/test_fastpheno.py +++ b/tests/resources/test_fastpheno.py @@ -25,7 +25,7 @@ def test_bands(self): "band_value": 0.025796278, "tree_height_proxy": 3.478942871, "ground_height_proxy": 49.10671997, - "band_month": "feb" + "band_month": "feb", }, { "site_name": "Pintendre", @@ -38,9 +38,9 @@ def test_bands(self): "band_value": 0.183586478, "tree_height_proxy": 2.383630037, "ground_height_proxy": 48.12341242131163, - "band_month": "feb" - } - ] + "band_month": "feb", + }, + ], } self.assertEqual(response.json, expected) @@ -67,7 +67,7 @@ def test_site_genotype_ids(self): "latitutde": 5178557, "genotype_id": "C", "tree_given_id": "11", - "external_link": "example" + "external_link": "example", }, { "site_name": "Pintendre", @@ -76,7 +76,7 @@ def test_site_genotype_ids(self): "latitutde": 5178486, "genotype_id": "C", "tree_given_id": "11", - "external_link": "example2" + "external_link": "example2", }, { "site_name": "Pintendre", @@ -85,9 +85,9 @@ def test_site_genotype_ids(self): "latitutde": 5278486, "genotype_id": "C", "tree_given_id": "B", - "external_link": "example3" - } - ] + "external_link": "example3", + }, + ], } self.assertEqual(response.json, expected) From ed2c235f4d6da6e721a1c112334a99bc04c706ea Mon Sep 17 00:00:00 2001 From: asherpasha Date: Tue, 10 Oct 2023 17:46:58 -0400 Subject: [PATCH 5/7] Update requirements. --- requirements.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index eee9b47..d0abb92 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,24 +2,24 @@ aniso8601==9.0.1 async-timeout==4.0.3 attrs==23.1.0 black==23.9.1 -blinker==1.6.2 +blinker==1.6.3 cachelib==0.9.0 certifi==2023.7.22 -charset-normalizer==3.2.0 +charset-normalizer==3.3.0 click==8.1.7 -coverage==7.3.1 +coverage==7.3.2 Deprecated==1.2.14 flake8==6.1.0 -Flask==2.3.3 -Flask-Caching==2.0.2 +Flask==3.0.0 +Flask-Caching==2.1.0 Flask-Cors==4.0.0 Flask-Limiter==3.5.0 flask-marshmallow==0.15.0 flask-restx==1.1.0 Flask-SQLAlchemy==3.1.1 -greenlet==2.0.2 +greenlet==3.0.0 idna==3.4 -importlib-resources==6.0.1 +importlib-resources==6.1.0 iniconfig==2.0.0 itsdangerous==2.1.2 Jinja2==3.1.2 @@ -34,9 +34,9 @@ mdurl==0.1.2 mypy-extensions==1.0.0 mysqlclient==2.2.0 ordered-set==4.1.0 -packaging==23.1 +packaging==23.2 pathspec==0.11.2 -platformdirs==3.10.0 +platformdirs==3.11.0 pluggy==1.3.0 pycodestyle==2.11.0 pyflakes==3.1.0 @@ -45,14 +45,14 @@ pyrsistent==0.19.3 pytest==7.4.2 python-dateutil==2.8.2 pytz==2023.3.post1 -redis==5.0.0 +redis==5.0.1 referencing==0.30.2 requests==2.31.0 -rich==13.5.3 -rpds-py==0.10.3 +rich==13.6.0 +rpds-py==0.10.4 six==1.16.0 SQLAlchemy==2.0.21 typing_extensions==4.8.0 -urllib3==2.0.5 -Werkzeug==2.3.7 +urllib3==2.0.6 +Werkzeug==3.0.0 wrapt==1.15.0 From 9554cd36d07dd3178ad773430b23ab7d97f74336 Mon Sep 17 00:00:00 2001 From: asherpasha Date: Tue, 10 Oct 2023 17:58:36 -0400 Subject: [PATCH 6/7] Updated requirements. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d0abb92..9f9324c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ click==8.1.7 coverage==7.3.2 Deprecated==1.2.14 flake8==6.1.0 -Flask==3.0.0 +Flask==2.3.3 Flask-Caching==2.1.0 Flask-Cors==4.0.0 Flask-Limiter==3.5.0 @@ -54,5 +54,5 @@ six==1.16.0 SQLAlchemy==2.0.21 typing_extensions==4.8.0 urllib3==2.0.6 -Werkzeug==3.0.0 +Werkzeug==2.3.7 wrapt==1.15.0 From 7b7a71b03196f731f9a6eb1270f6cb859c5ea6bd Mon Sep 17 00:00:00 2001 From: asherpasha Date: Tue, 17 Oct 2023 16:11:13 -0400 Subject: [PATCH 7/7] MySQL 8.1 and other updates. --- .github/workflows/bar-api.yml | 2 +- Dockerfile | 2 +- docker-compose.yml | 4 ++-- requirements.txt | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bar-api.yml b/.github/workflows/bar-api.yml index 6cc09d1..abd298e 100644 --- a/.github/workflows/bar-api.yml +++ b/.github/workflows/bar-api.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 strategy: matrix: - python-version: [3.8, 3.9, 3.10.13, 3.11] + python-version: [3.8, 3.9, 3.10.13, 3.11, 3.12] services: redis: diff --git a/Dockerfile b/Dockerfile index 3612126..5aea506 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.5-bookworm +FROM python:3.12-bookworm WORKDIR /usr/src/app diff --git a/docker-compose.yml b/docker-compose.yml index 394398f..a2fea40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.7" services: mysqldb: - image: mysql:8.0.34 + image: mysql:8.1.0 container_name: BAR_mysqldb # Must use this for mariadb client to connect command: --default-authentication-plugin=mysql_native_password @@ -12,7 +12,7 @@ services: - MYSQL_ROOT_PASSWORD=root redis: - image: redis:7.2.0 + image: redis:7.2.1 container_name: BAR_redis restart: always ports: diff --git a/requirements.txt b/requirements.txt index 9f9324c..4b59343 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ aniso8601==9.0.1 async-timeout==4.0.3 attrs==23.1.0 -black==23.9.1 +black==23.10.0 blinker==1.6.3 cachelib==0.9.0 certifi==2023.7.22 @@ -38,7 +38,7 @@ packaging==23.2 pathspec==0.11.2 platformdirs==3.11.0 pluggy==1.3.0 -pycodestyle==2.11.0 +pycodestyle==2.11.1 pyflakes==3.1.0 Pygments==2.16.1 pyrsistent==0.19.3 @@ -49,10 +49,10 @@ redis==5.0.1 referencing==0.30.2 requests==2.31.0 rich==13.6.0 -rpds-py==0.10.4 +rpds-py==0.10.6 six==1.16.0 -SQLAlchemy==2.0.21 +SQLAlchemy==2.0.22 typing_extensions==4.8.0 -urllib3==2.0.6 +urllib3==2.0.7 Werkzeug==2.3.7 wrapt==1.15.0