diff --git a/.github/workflows/pull-request-workflow.yml b/.github/workflows/pull-request-workflow.yml
deleted file mode 100644
index 54eb30c..0000000
--- a/.github/workflows/pull-request-workflow.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Pre-merge Review
-
-on: [pull_request]
-
-jobs:
- pull-request-checks:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- - name: Setup python
- uses: actions/setup-python@v5
- with:
- python-version: '3.12'
- - name: Install dependencies
- run: pip install poetry && poetry install --sync
- - name: Run styling
- run: make style-check
- - name: Run type checks
- run: make type-check
- - name: Start services
- run: IS_CI="True" make init
- - name: Check migration scripts are added for modified schemas
- run: make check-db-schema
- - name: Run tests
- env:
- TEST_STRIPE_SECRET_KEY: ${{ secrets.TEST_STRIPE_SECRET_KEY }}
- run: |
- echo STRIPE_SECRET_KEY=${{ secrets.TEST_STRIPE_SECRET_KEY }} >> .env.local
- echo STRIPE_DEVICE_NAME=CI >> .env.local
- DEPLOYMENT_ENV=testing poetry run pytest
- - name: cleanup
- if: ${{ always() }}
- run: |
- docker compose -f env-prep/docker-compose-dev.yml -p vlm-project down --rmi all --volumes
- docker system prune --force --volumes
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
new file mode 100644
index 0000000..e3f7beb
--- /dev/null
+++ b/.github/workflows/pull-request.yml
@@ -0,0 +1,119 @@
+name: Pre-merge Review
+
+on: [pull_request]
+
+jobs:
+ setup:
+ runs-on: ubuntu-latest
+ outputs:
+ python-cache-key: ${{ steps.cache-deps.outputs.cache-hit }}
+ steps:
+ # Step 1: Checkout code
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ # Step 2: Set up Python with caching
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Cache Python dependencies
+ id: cache-deps
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/pypoetry/virtualenvs
+ .venv
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+
+ # Step 3: Install dependencies
+ - name: Install dependencies
+ run: |
+ pip install poetry
+ poetry sync
+
+ lint:
+ needs: setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Use Python cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/pypoetry/virtualenvs
+ .venv
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
+
+ - name: Install Poetry
+ run: |
+ pip install poetry
+
+ - name: Run styling checks
+ run: make style-check
+
+ type-check:
+ needs: setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Use Python cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/pypoetry/virtualenvs
+ .venv
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
+
+ - name: Install Poetry
+ run: |
+ pip install poetry
+
+ - name: Run type checks
+ run: make type-check
+
+ test:
+ needs: setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Use Python cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/pypoetry/virtualenvs
+ .venv
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
+
+ - name: Install Poetry
+ run: |
+ pip install poetry
+
+ - name: Start services
+ run: |
+ IS_CI="True" make init
+
+ - name: Check database schema migrations
+ run: make check-db-schema
+
+ - name: Run tests
+ env:
+ TEST_STRIPE_SECRET_KEY: ${{ secrets.TEST_STRIPE_SECRET_KEY }}
+ run: |
+ echo STRIPE_SECRET_KEY=${{ secrets.TEST_STRIPE_SECRET_KEY }} >> .env.local
+ echo STRIPE_DEVICE_NAME=CI >> .env.local
+ DEPLOYMENT_ENV=testing poetry run pytest
+ - name: Cleanup resources
+ if: ${{ always() }}
+ run: |
+ make kill
+ docker system prune --force --volumes
diff --git a/Dockerfile b/Dockerfile
index 8682812..f4142e3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.12-bookworm as builder
+FROM python:3.12-bookworm AS builder
RUN pip install poetry==1.4.2
@@ -14,7 +14,7 @@ RUN poetry add psycopg2-binary
RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --without dev --no-root
-FROM python:3.12-slim-bookworm as runtime
+FROM python:3.12-slim-bookworm AS runtime
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"
diff --git a/Makefile b/Makefile
index 0fa108a..edcd6c5 100644
--- a/Makefile
+++ b/Makefile
@@ -35,7 +35,7 @@ dev-p:
poetry run uvicorn virtual_labs.api:app --reload
init:
- ./dev-init.sh
+ ./dev-init.sh $(filter-out $@,$(MAKECMDGOALS))
kill:
cd env-prep && docker compose -f docker-compose-dev.yml -p vlm-project down --remove-orphans --volumes
diff --git a/README.md b/README.md
index 6e06154..fc0554d 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,24 @@ Make sure you have the following dependencies installed:
- [jq](https://jqlang.github.io/jq/download/)
- Stripe API key (`STRIPE_SECRET_KEY` test key and `STRIPE_DEVICE_NAME=dev` in `./.env.local`)
+# Configuration
+
+**Running in Macos (M4):**
+For users running the virtual-lab-api on macOS with the M4 chip, the docker-compose configuration requires specific updates to ensure compatibility with the architecture and resource management. Please apply the following changes:
+
+**Delta service:**
+Add the following property to ensure the service runs using the correct architecture:
+```
+platform: linux/amd64
+```
+
+**Elasticsearch service:**
+Include the following environment variables to configure memory usage and disable SVE (Scalable Vector Extensions):
+```
+ES_JAVA_OPTS: "-Xms512m -Xmx512m -XX:UseSVE=0"
+CLI_JAVA_OPTS: "-XX:UseSVE=0"
+```
+
# Development
1. Install the dependencies
@@ -24,6 +42,10 @@ Make sure you have the following dependencies installed:
```bash
make init
```
+ If you are using a machine with Apple ships (as M4), use this command instead:
+ ```
+ make init amd
+ ```
3. Run db migrations (this also initializes the database)
```
@@ -33,6 +55,7 @@ Make sure you have the following dependencies installed:
This should start the server on port 8000 (http://127.0.0.1:8000)
The docs will be available at http://127.0.0.1:8000/docs#/
+
# Retrieving tokens for test users
The token for user `test` (only user right now that can create virtual labs) is already copied to your clipboard when you run `make init`.
@@ -109,7 +132,6 @@ For further details on working with Setup Intents and managing payment methods,
stripe setup_intents confirm seti_1PFtBwFjhkSGAqrAUHCvTAAA \
--payment-method=pm_card_visa
```
-
# IDE Setup
## VS Code
diff --git a/dev-init.sh b/dev-init.sh
index b664d2f..1b5ef3a 100755
--- a/dev-init.sh
+++ b/dev-init.sh
@@ -8,11 +8,16 @@ KC_REALM_NAME="obp-realm"
CLIENT_ID="obpapp"
CLIENT_SECRET="obp-secret"
+if [ "$1" == "amd" ]; then
+ COMPOSE_FILE="env-prep/docker-compose-dev-amd.yml"
+else
+ COMPOSE_FILE="env-prep/docker-compose-dev.yml"
+fi
# Start containers
chmod +x ./env-prep/init/init-aws.sh
ls -l ./env-prep/init/init-aws.sh
-docker compose -f env-prep/docker-compose-dev.yml -p vlm-project up --wait
+docker compose -f "$COMPOSE_FILE" -p vlm-project up --wait
# Check that delta ready to accept connections
echo "Checking that delta is ready to accept connections..."
@@ -35,6 +40,7 @@ curl -XPUT \
}'
echo "Initialize nexus"
+
python3 env-prep/init/init.py
echo "📦 Initialize Vl database"
diff --git a/env-prep/docker-compose-dev-amd.yml b/env-prep/docker-compose-dev-amd.yml
new file mode 100644
index 0000000..c896a43
--- /dev/null
+++ b/env-prep/docker-compose-dev-amd.yml
@@ -0,0 +1,210 @@
+volumes:
+ keycloak_data: {}
+ vlm_data: {}
+ delta_data: {}
+
+networks:
+ ls:
+ ipam:
+ config:
+ # Specify the subnet range for IP address allocation
+ - subnet: 10.0.2.0/24
+
+services:
+ keycloak-db:
+ image: postgres:latest
+ container_name: keycloak-db
+ environment:
+ POSTGRES_USER: keycloak
+ POSTGRES_PASSWORD: keycloak
+ POSTGRES_DB: keycloak
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U keycloak"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+ restart: unless-stopped
+ volumes:
+ - keycloak_data:/var/lib/postgresql/data
+ networks:
+ - ls
+
+ keycloak:
+ image: quay.io/keycloak/keycloak:24.0
+ container_name: keycloak
+ environment:
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ DB_VENDOR: postgres
+ DB_ADDR: keycloak-db
+ DB_DATABASE: keycloak
+ DB_USER: keycloak
+ DB_PASSWORD: keycloak
+ depends_on:
+ - keycloak-db
+ command:
+ - start-dev
+ - --http-port=9090
+ - --hostname=keycloak
+ - --hostname-port=9090
+ - --hostname-strict-backchannel=true
+ - --import-realm
+ ports:
+ - "9090:9090"
+ networks:
+ - ls
+ volumes:
+ - ./realm-export.json:/opt/keycloak/data/import/realm-import.json
+
+ virtual-lab-db:
+ image: postgres:latest
+ container_name: vlm-db
+ environment:
+ POSTGRES_USER: vlm
+ POSTGRES_PASSWORD: vlm
+ PGDATA: vlm_data
+ POSTGRES_DB: vlm
+ ports:
+ - 15432:5432
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U vlm"]
+ interval: 10s
+ timeout: 5s
+ retries: 3
+ restart: unless-stopped
+ volumes:
+ - vlm_data:/var/lib/postgresql/data
+ networks:
+ - ls
+
+ mailpit:
+ image: axllent/mailpit
+ container_name: test-mail-server
+ restart: always
+ volumes:
+ - ./email-data:/email-data
+ ports:
+ - 8025:8025 # UI to see sent emails
+ - 1025:1025 # test smtp server
+ environment:
+ MP_MAX_MESSAGES: 5000
+ MP_DATA_FILE: /email-data/mailpit.db
+ MP_SMTP_AUTH_ACCEPT_ANY: 1
+ MP_SMTP_AUTH_ALLOW_INSECURE: 1
+
+ delta:
+ container_name: delta
+ platform: linux/amd64
+ depends_on:
+ keycloak:
+ condition: service_started
+ elasticsearch:
+ condition: service_healthy
+ blazegraph:
+ condition: service_started
+ postgres:
+ condition: service_started
+ localstack:
+ condition: service_started
+ environment:
+ DELTA_PLUGINS: "/opt/docker/plugins/"
+ DELTA_EXTERNAL_CONF: "/config/delta-postgres.conf"
+ KAMON_ENABLED: "false"
+ image: bluebrain/nexus-delta:latest
+ entrypoint:
+ - '/bin/bash'
+ - '-c'
+ - '/opt/docker/bin/delta-app -Xmx4G'
+ ports:
+ - 8080:8080
+ volumes:
+ - ./config:/config
+ - /tmp:/default-volume
+ dns:
+ # Set the DNS server to be the LocalStack container, for host resolution
+ - 10.0.2.20
+ networks:
+ - ls
+
+ elasticsearch:
+ image: docker.elastic.co/elasticsearch/elasticsearch:8.12.1
+ environment:
+ ES_JAVA_OPTS: "-Xms512m -Xmx512m -XX:UseSVE=0"
+ CLI_JAVA_OPTS: "-XX:UseSVE=0"
+ discovery.type: "single-node"
+ bootstrap.memory_lock: "true"
+ xpack.security.enabled: "true"
+ ingest.geoip.downloader.enabled: "false"
+ ELASTIC_PASSWORD: "password"
+ healthcheck:
+ test: [ "CMD", "curl", "-f", "http://elastic:password@localhost:9200/" ]
+ interval: 1s
+ timeout: 2s
+ retries: 60
+ ports:
+ - 9200:9200
+ deploy:
+ resources:
+ limits:
+ memory: 4G
+ networks:
+ - ls
+
+ blazegraph:
+ image: bluebrain/blazegraph-nexus:2.1.6-RC
+ environment:
+ JAVA_OPTS: "-DjettyXml=/config/jetty.xml -Djava.awt.headless=true -XX:MaxDirectMemorySize=300m -Xms4g -Xmx4g -XX:+UseG1GC"
+ ports:
+ - 9999:9999
+ volumes:
+ - ./config:/config
+ networks:
+ - ls
+
+ postgres:
+ image: library/postgres:15.6
+ environment:
+ POSTGRES_USER: "postgres"
+ POSTGRES_PASSWORD: "postgres"
+ ports:
+ - 5432:5432
+ volumes:
+ - delta_data:/var/lib/postgresql/data
+ networks:
+ - ls
+
+ stripe-cli:
+ image: stripe/stripe-cli
+ container_name: stripe
+ environment:
+ STRIPE_CLI_TELEMETRY_OPTOUT: 1
+ STRIPE_API_KEY: ${STRIPE_SECRET_KEY}
+ STRIPE_DEVICE_NAME: ${STRIPE_DEVICE_NAME}
+ volumes:
+ - ./stripe-data:/stripe-data
+ entrypoint:
+ - '/bin/sh'
+ - '-c'
+ - |
+ echo STRIPE_WEBHOOK_SECRET=$(stripe listen --print-secret) > /stripe-data/.env.local &&
+ stripe listen --forward-to http://host.docker.internal:8000/payments/webhook
+ network_mode: host
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ - "172.17.0.1:host-gateway"
+
+ localstack:
+ image: localstack/localstack:3.5
+ environment:
+ AWS_ACCESS_KEY_ID: "MY_ACCESS_KEY"
+ AWS_SECRET_ACCESS_KEY: "CHUTCHUT"
+ SERVICES: "s3:4566"
+ ports:
+ - 4566:4566
+ volumes:
+ - "./init/init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh" # ready hook
+ - "/var/run/docker.sock:/var/run/docker.sock"
+ networks:
+ ls:
+ # Set the container IP address in the 10.0.2.0/24 subnet
+ ipv4_address: 10.0.2.20
diff --git a/env-prep/docker-compose-dev.yml b/env-prep/docker-compose-dev.yml
index 71da581..58b7a3c 100644
--- a/env-prep/docker-compose-dev.yml
+++ b/env-prep/docker-compose-dev.yml
@@ -1,5 +1,3 @@
-version: "3.9"
-
volumes:
keycloak_data: {}
vlm_data: {}
@@ -105,8 +103,6 @@ services:
condition: service_started
postgres:
condition: service_started
- storage-service:
- condition: service_started
localstack:
condition: service_started
environment:
@@ -117,9 +113,7 @@ services:
entrypoint:
- '/bin/bash'
- '-c'
- - |
- ln -sf /opt/docker/plugins/disabled/project-deletion.jar /opt/docker/plugins/project-deletion.jar &&
- /opt/docker/bin/delta-app -Xmx4G
+ - '/opt/docker/bin/delta-app -Xmx4G'
ports:
- 8080:8080
volumes:
@@ -134,7 +128,7 @@ services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.1
environment:
- ES_JAVA_OPTS: "-Xmx2G"
+ ES_JAVA_OPTS: "-Xms2G"
discovery.type: "single-node"
bootstrap.memory_lock: "true"
xpack.security.enabled: "true"
@@ -196,21 +190,7 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
- "172.17.0.1:host-gateway"
-
- storage-service:
- container_name: "nexus-storage-service"
- image: bluebrain/nexus-storage:latest
- environment:
- STORAGE_CONFIG_FILE: "/config/storage.conf"
- KAMON_ENABLED: "false"
- entrypoint: [ "./bin/storage",
- "-Dkamon.modules.prometheus-reporter.enabled=false",
- "-Dkamon.modules.jaeger.enabled=false" ]
- ports:
- - 8090:8090
- volumes:
- - ./config:/config
-
+
localstack:
image: localstack/localstack:3.5
environment:
diff --git a/poetry.lock b/poetry.lock
index 6a1b133..2fc19b1 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "aiosmtplib"
@@ -6,6 +6,7 @@ version = "2.0.2"
description = "asyncio SMTP client"
optional = false
python-versions = ">=3.7,<4.0"
+groups = ["main"]
files = [
{file = "aiosmtplib-2.0.2-py3-none-any.whl", hash = "sha256:1e631a7a3936d3e11c6a144fb8ffd94bb4a99b714f2cb433e825d88b698e37bc"},
{file = "aiosmtplib-2.0.2.tar.gz", hash = "sha256:138599a3227605d29a9081b646415e9e793796ca05322a78f69179f0135016a3"},
@@ -21,6 +22,7 @@ version = "1.13.2"
description = "A database migration tool for SQLAlchemy."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
@@ -40,6 +42,7 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@@ -51,6 +54,7 @@ version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
@@ -71,6 +75,7 @@ version = "0.2.2"
description = "Python decorator for async properties."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"},
{file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"},
@@ -82,6 +87,7 @@ version = "0.29.0"
description = "An asyncio PostgreSQL driver"
optional = false
python-versions = ">=3.8.0"
+groups = ["main"]
files = [
{file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
{file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
@@ -136,6 +142,7 @@ version = "0.29.1"
description = "asyncpg stubs"
optional = false
python-versions = ">=3.8,<4.0"
+groups = ["main"]
files = [
{file = "asyncpg_stubs-0.29.1-py3-none-any.whl", hash = "sha256:cce994d5a19394249e74ae8d252bde3c77cee0ddfc776cc708b724fdb4adebb6"},
{file = "asyncpg_stubs-0.29.1.tar.gz", hash = "sha256:686afcc0af3a2f3c8e393cd850e0de430e5a139ce82b2f28ef8f693ecdf918bf"},
@@ -151,6 +158,7 @@ version = "1.8.2"
description = "Fast, simple object-to-object and broadcast signaling"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"},
{file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"},
@@ -162,6 +170,7 @@ version = "2024.8.30"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
+groups = ["main", "dev"]
files = [
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
@@ -173,6 +182,8 @@ version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
+markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -252,6 +263,7 @@ version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
@@ -263,6 +275,7 @@ version = "3.3.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7.0"
+groups = ["main", "dev"]
files = [
{file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
@@ -362,6 +375,7 @@ version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
@@ -376,10 +390,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "sys_platform == \"win32\""}
[[package]]
name = "cryptography"
@@ -387,6 +403,7 @@ version = "43.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"},
{file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"},
@@ -436,6 +453,7 @@ version = "5.5"
description = "This package provides a DateTime data type, as known from Zope. Unless you need to communicate with Zope APIs, you're probably better off using Python's built-in datetime module."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120"},
{file = "DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3"},
@@ -451,6 +469,7 @@ version = "2.1.0"
description = "A library to handle automated deprecations"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
{file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
@@ -465,6 +484,7 @@ version = "0.3.8"
description = "Distribution utilities"
optional = false
python-versions = "*"
+groups = ["dev"]
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
@@ -476,6 +496,7 @@ version = "2.6.1"
description = "DNS toolkit"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
{file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
@@ -496,6 +517,7 @@ version = "2.2.0"
description = "A robust email address syntax and deliverability validation library."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
{file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
@@ -511,6 +533,7 @@ version = "0.110.3"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"},
{file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"},
@@ -541,6 +564,7 @@ version = "1.4.1"
description = "Simple lightweight mail library for FastApi"
optional = false
python-versions = ">=3.8.1,<4.0"
+groups = ["main"]
files = [
{file = "fastapi_mail-1.4.1-py3-none-any.whl", hash = "sha256:fa5ef23b2dea4d3ba4587f4bbb53f8f15274124998fb4e40629b3b636c76c398"},
{file = "fastapi_mail-1.4.1.tar.gz", hash = "sha256:9095b713bd9d3abb02fe6d7abb637502aaf680b52e177d60f96273ef6bc8bb70"},
@@ -565,6 +589,7 @@ version = "3.16.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"},
{file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"},
@@ -581,6 +606,8 @@ version = "3.1.0"
description = "Lightweight in-process concurrent programming"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"
files = [
{file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"},
{file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"},
@@ -660,6 +687,7 @@ version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -671,6 +699,7 @@ version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
@@ -692,6 +721,7 @@ version = "0.6.1"
description = "A collection of framework independent HTTP protocol utils."
optional = false
python-versions = ">=3.8.0"
+groups = ["main"]
files = [
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
@@ -740,6 +770,7 @@ version = "0.27.2"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
@@ -765,6 +796,7 @@ version = "2.6.0"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"},
{file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"},
@@ -779,6 +811,7 @@ version = "3.8"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
+groups = ["main", "dev"]
files = [
{file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
@@ -790,6 +823,7 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -801,6 +835,7 @@ version = "2.2.0"
description = "Safely pass data to untrusted environments and back."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
@@ -812,6 +847,7 @@ version = "3.1.4"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
@@ -829,6 +865,7 @@ version = "1.5.6"
description = "Implementation of JOSE Web standards"
optional = false
python-versions = ">= 3.8"
+groups = ["main"]
files = [
{file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"},
{file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"},
@@ -844,6 +881,7 @@ version = "0.7.2"
description = "Python logging made (stupidly) simple"
optional = false
python-versions = ">=3.5"
+groups = ["main"]
files = [
{file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
{file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
@@ -862,6 +900,7 @@ version = "1.3.5"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
{file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
@@ -881,6 +920,7 @@ version = "2.1.5"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
{file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
@@ -950,6 +990,7 @@ version = "1.11.2"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
{file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
@@ -996,6 +1037,7 @@ version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
+groups = ["main", "dev"]
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -1007,6 +1049,7 @@ version = "1.9.1"
description = "Node.js virtual environment builder"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["dev"]
files = [
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
@@ -1018,6 +1061,7 @@ version = "3.10.7"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"},
{file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"},
@@ -1084,6 +1128,7 @@ version = "24.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
@@ -1095,6 +1140,7 @@ version = "4.3.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"},
{file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"},
@@ -1111,6 +1157,7 @@ version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -1126,6 +1173,7 @@ version = "3.8.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
+groups = ["dev"]
files = [
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
@@ -1144,6 +1192,7 @@ version = "2.9.9"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
@@ -1225,6 +1274,8 @@ version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
+markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -1236,6 +1287,7 @@ version = "2.9.1"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"},
{file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"},
@@ -1260,6 +1312,7 @@ version = "2.23.3"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"},
{file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"},
@@ -1361,6 +1414,7 @@ version = "2.9.0"
description = "Extra Pydantic types."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0"},
{file = "pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b"},
@@ -1383,6 +1437,7 @@ version = "2.5.2"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"},
{file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"},
@@ -1403,6 +1458,7 @@ version = "2.9.0"
description = "JSON Web Token implementation in Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"},
{file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"},
@@ -1420,6 +1476,7 @@ version = "8.0.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"},
{file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"},
@@ -1440,6 +1497,7 @@ version = "0.21.1"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"},
{file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"},
@@ -1458,6 +1516,7 @@ version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
@@ -1472,6 +1531,7 @@ version = "4.3.0"
description = "python-keycloak is a Python package providing access to the Keycloak API."
optional = false
python-versions = "<4.0,>=3.8"
+groups = ["main"]
files = [
{file = "python_keycloak-4.3.0-py3-none-any.whl", hash = "sha256:6dc1e89c38346a90bb4386850381d84fedb489a7dea19d561aaad5882ceeed72"},
{file = "python_keycloak-4.3.0.tar.gz", hash = "sha256:c187ceee7d79f0d76d9c2ed26e3d10ce35e42a407896bf5ec15b8fb24fc0c9ce"},
@@ -1491,6 +1551,7 @@ version = "0.0.9"
description = "A streaming multipart parser for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
@@ -1505,6 +1566,7 @@ version = "2024.2"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
{file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
@@ -1516,6 +1578,7 @@ version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -1578,6 +1641,7 @@ version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -1599,6 +1663,7 @@ version = "1.0.0"
description = "A utility belt for advanced users of python-requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+groups = ["main"]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
@@ -1613,6 +1678,7 @@ version = "0.2.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
@@ -1639,6 +1705,7 @@ version = "2.14.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "sentry_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:b8bc3dc51d06590df1291b7519b85c75e2ced4f28d9ea655b6d54033503b5bf4"},
{file = "sentry_sdk-2.14.0.tar.gz", hash = "sha256:1e0e2eaf6dad918c7d1e0edac868a7bf20017b177f242cefe2a6bcd47955961d"},
@@ -1691,6 +1758,7 @@ version = "74.1.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"},
{file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"},
@@ -1711,6 +1779,7 @@ version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1722,6 +1791,7 @@ version = "2.0.34"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db"},
{file = "SQLAlchemy-2.0.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b"},
@@ -1775,7 +1845,10 @@ files = [
]
[package.dependencies]
-greenlet = {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""}
+greenlet = [
+ {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"},
+ {version = "!=0.4.17", optional = true, markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""},
+]
mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""}
typing-extensions = ">=4.6.0"
@@ -1810,6 +1883,7 @@ version = "0.37.2"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
@@ -1827,6 +1901,7 @@ version = "9.12.0"
description = "Python bindings for the Stripe API"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "stripe-9.12.0-py2.py3-none-any.whl", hash = "sha256:14dd20ef1e6386a52e1f7aea07701fc6a80223706d72e3509e4966adb599e138"},
{file = "stripe-9.12.0.tar.gz", hash = "sha256:cbc526abd0f001c920c323ba7c40cce3dee1647d920e9dbecae3488f37367524"},
@@ -1842,6 +1917,7 @@ version = "2.32.0.20240907"
description = "Typing stubs for requests"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "types-requests-2.32.0.20240907.tar.gz", hash = "sha256:ff33935f061b5e81ec87997e91050f7b4af4f82027a7a7a9d9aaea04a963fdf8"},
{file = "types_requests-2.32.0.20240907-py3-none-any.whl", hash = "sha256:1d1e79faeaf9d42def77f3c304893dea17a97cae98168ac69f3cb465516ee8da"},
@@ -1856,6 +1932,7 @@ version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@@ -1867,6 +1944,7 @@ version = "5.10.0"
description = "Ultra fast JSON encoder and decoder for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
{file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
@@ -1954,6 +2032,7 @@ version = "2.2.2"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
@@ -1971,6 +2050,7 @@ version = "1.30"
description = "UUID object and generation functions (Python 2.3 or higher)"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "uuid-1.30.tar.gz", hash = "sha256:1f87cc004ac5120466f36c5beae48b4c48cc411968eed0eaecd3da82aa96193f"},
]
@@ -1981,6 +2061,7 @@ version = "0.27.1"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"},
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"},
@@ -2006,6 +2087,8 @@ version = "0.20.0"
description = "Fast implementation of asyncio event loop on top of libuv"
optional = false
python-versions = ">=3.8.0"
+groups = ["main"]
+markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\""
files = [
{file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996"},
{file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b"},
@@ -2050,6 +2133,7 @@ version = "20.26.4"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"},
{file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"},
@@ -2070,6 +2154,7 @@ version = "0.24.0"
description = "Simple, modern and high performance file watching and code reload in python."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"},
{file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"},
@@ -2165,6 +2250,7 @@ version = "13.0.1"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"},
{file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"},
@@ -2260,6 +2346,8 @@ version = "1.1.0"
description = "A small Python utility to set file creation time on Windows"
optional = false
python-versions = ">=3.5"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
files = [
{file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
@@ -2274,6 +2362,7 @@ version = "7.0.3"
description = "Interfaces for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "zope.interface-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b"},
{file = "zope.interface-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca"},
@@ -2320,6 +2409,6 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
-lock-version = "2.0"
+lock-version = "2.1"
python-versions = "^3.12"
-content-hash = "7a5c637634fa40128abb51988b39a41e7c31f44033fe0978bc30f18884503906"
+content-hash = "25470feca8b1196b5b12730f34329c93eed9e9c54c0f7c81e1bb88dc20894203"
diff --git a/pyproject.toml b/pyproject.toml
index 233adae..f13f497 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,6 +27,7 @@ asyncpg = "^0.29.0"
asyncpg-stubs = "^0.29.1"
stripe = "^9.6.0"
sentry-sdk = {extras = ["fastapi"], version = "^2.14.0"}
+requests = "^2.32.3"
[tool.poetry.group.dev.dependencies]
diff --git a/virtual_labs/domain/user.py b/virtual_labs/domain/user.py
index 6e8374d..d30b579 100644
--- a/virtual_labs/domain/user.py
+++ b/virtual_labs/domain/user.py
@@ -60,3 +60,4 @@ class UserAgentResponse(BaseModel):
family_name: str
name: str
createdAt: datetime
+ type: list[str]
diff --git a/virtual_labs/infrastructure/email/assets/logo.png b/virtual_labs/infrastructure/email/assets/logo.png
index d3dc90b..7257e1b 100644
Binary files a/virtual_labs/infrastructure/email/assets/logo.png and b/virtual_labs/infrastructure/email/assets/logo.png differ
diff --git a/virtual_labs/infrastructure/email/templates/invitation_template.html b/virtual_labs/infrastructure/email/templates/invitation_template.html
index e9f6b62..a0e3e1d 100644
--- a/virtual_labs/infrastructure/email/templates/invitation_template.html
+++ b/virtual_labs/infrastructure/email/templates/invitation_template.html
@@ -70,6 +70,10 @@
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
+ .logo {
+ width: 147px;
+ height: 50px;
+ }
.main {
color: #003A8C;
@@ -123,13 +127,14 @@
.invited-to {
display: inline-block;
padding: 0 15px;
- padding-top: 4px;
background-color: #003a8c;
color: white;
border-radius: 36px;
line-height: 22px;
text-align: center;
+ vertical-align: middle;
padding-top: 4px;
+ padding-bottom: 4px;
}
.light {
@@ -343,9 +348,9 @@
|
- ![Open Blue Brain Platform](cid:logo)
+ ![Open Blue Brain Platform](cid:logo)
-
+
@@ -394,12 +399,11 @@
- What is Open Blue Brain Platform?
+ What is Open Brain Platform?
- OBBP is the next generation platform that connects explorative features
- with a
- powerful set of models building tools that lead to simulation experiments. From ion
- channels to the whole brain, OBBP empower scientific in their daily tasks. Explore,
+ OBP is the next generation platform that connects explorative features
+ with a powerful set of models building tools that lead to simulation experiments.
+ From ion channels to the whole brain, OBP empower scientific in their daily tasks. Explore,
collect and use real biological data to digitally simulate neurons.
diff --git a/virtual_labs/infrastructure/settings.py b/virtual_labs/infrastructure/settings.py
index f6b87bd..61bbd4e 100644
--- a/virtual_labs/infrastructure/settings.py
+++ b/virtual_labs/infrastructure/settings.py
@@ -63,7 +63,7 @@ class Settings(BaseSettings):
INVITE_JWT_SECRET: str = "TEST_JWT_SECRET"
INVITE_EXPIRES_IN_DAYS: int = 7
- INVITE_LINK_BASE: str = "http://localhost:3000/"
+ INVITE_LINK_BASE: str = "http://localhost:3000"
STRIPE_SECRET_KEY: str = ""
STRIPE_DEVICE_NAME: str = ""
diff --git a/virtual_labs/tests/agent/test_get_agent.py b/virtual_labs/tests/agent/test_get_agent.py
index a8720b0..2cfd04c 100644
--- a/virtual_labs/tests/agent/test_get_agent.py
+++ b/virtual_labs/tests/agent/test_get_agent.py
@@ -20,6 +20,7 @@ async def test_get_agent(async_test_client: AsyncClient) -> None:
"given_name": user,
"family_name": user,
"name": f"{user} {user}",
+ "type": ["Agent", "Person"],
}
gotten_agent_response = response.json()["data"]
diff --git a/virtual_labs/usecases/invites/invitation_handler.py b/virtual_labs/usecases/invites/invitation_handler.py
index 5d4f6b1..cf92590 100644
--- a/virtual_labs/usecases/invites/invitation_handler.py
+++ b/virtual_labs/usecases/invites/invitation_handler.py
@@ -65,6 +65,7 @@ async def invitation_handler(
"status": "already_accepted",
},
)
+
if vlab_invite.user_email != auth[0].email:
raise UserMismatch(
"Invite email not match the authenticated user email"
| |