Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework/Gradio: Add basic example about Gradio, reading sys.summits #507

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ updates:
schedule:
interval: "daily"

- directory: "/framework/gradio"
package-ecosystem: "pip"
schedule:
interval: "daily"

- directory: "/framework/streamlit"
package-ecosystem: "pip"
schedule:
Expand Down
74 changes: 74 additions & 0 deletions .github/workflows/framework-gradio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Gradio

on:
pull_request:
branches: ~
paths:
- '.github/workflows/framework-gradio.yml'
- 'framework/gradio/**'
- '/requirements.txt'
push:
branches: [ main ]
paths:
- '.github/workflows/framework-gradio.yml'
- 'framework/gradio/**'
- '/requirements.txt'

# Allow job to be triggered manually.
workflow_dispatch:

# Run job each night after CrateDB nightly has been published.
schedule:
- cron: '0 3 * * *'

# Cancel in-progress jobs when pushing to the same branch.
concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}

jobs:
test:
name: "
Python: ${{ matrix.python-version }}
CrateDB: ${{ matrix.cratedb-version }}
on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ 'ubuntu-latest' ]
python-version: [ '3.10', '3.11', '3.12' ]
cratedb-version: [ 'nightly' ]

services:
cratedb:
image: crate/crate:${{ matrix.cratedb-version }}
ports:
- 4200:4200
- 5432:5432
env:
CRATE_HEAP_SIZE: 4g

steps:

- name: Acquire sources
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
cache: 'pip'
cache-dependency-path: |
requirements.txt
framework/gradio/requirements.txt
framework/gradio/requirements-dev.txt

- name: Install utilities
run: |
pip install -r requirements.txt

- name: Validate framework/gradio
run: |
ngr test --accept-no-venv framework/gradio
1 change: 1 addition & 0 deletions framework/gradio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/flagged
62 changes: 62 additions & 0 deletions framework/gradio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Gradio with CrateDB Example

## About
Demonstrate connectivity from Gradio to CrateDB.

## Configuration
Configure database connection address and credentials by using
an SQLAlchemy connection string, see variable `CRATEDB_SQLALCHEMY_URL`
in `basic_sys_summits.py`. Please make sure to use valid credentials
matching your environment.

## Usage

### CrateDB on localhost
To start a CrateDB instance on your machine, invoke:
```shell
docker run -it --rm \
--publish=4200:4200 --publish=5432:5432 \
--env=CRATE_HEAP_SIZE=2g \
crate:latest -Cdiscovery.type=single-node
```

### CrateDB Cloud
Please have a look at the [basic_sys_summits.py](basic_sys_summits.py) program
as a blueprint. It includes a `CRATEDB_SQLALCHEMY_URL` variable definition
that configures the application to connect to CrateDB Cloud.
```python
CRATEDB_SQLALCHEMY_URL = "crate://admin:g_,8.F0fNbVSk0.*!n54S5c,@example.gke1.us-central1.gcp.cratedb.net:4200?ssl=true"```
```

Install dependencies.
```shell
pip install -r requirements.txt
```

Invoke Gradio to serve the application.
```shell
gradio basic_sys_summits.py
```

## Screenshot

Enjoy the list of mountains.

![image](https://github.com/crate/cratedb-examples/assets/453543/af417966-b694-45ec-9391-f0e99a2ac014)


## Development

Acquire `cratedb-example` repository, and set up development sandbox.
```shell
git clone https://github.com/crate/cratedb-examples
cd cratedb-examples
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Invoke the integration test cases.
```shell
ngr test framework/gradio
```
48 changes: 48 additions & 0 deletions framework/gradio/basic_sys_summits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
A basic Gradio application connecting to CrateDB using SQLAlchemy.

It reads the built-in `sys.summits` table into a dataframe, and
displays its contents.

- https://www.gradio.app/guides/connecting-to-a-database
- https://www.gradio.app/docs/gradio/dataframe
- https://cratedb.com/docs/sqlalchemy-cratedb/
"""
import gradio as gr
import pandas as pd

# Connect to CrateDB on localhost.
CRATEDB_SQLALCHEMY_URL = "crate://localhost:4200"

# Connect to CrateDB Cloud.
# CRATEDB_SQLALCHEMY_URL = "crate://admin:g_,8.F0fNbVSk0.*!n54S5c,@example.gke1.us-central1.gcp.cratedb.net:4200?ssl=true"


def get_sys_summits():
"""
Query the database using pandas.
"""
return pd.read_sql('SELECT * FROM "sys"."summits";', con=CRATEDB_SQLALCHEMY_URL)


def get_dataframe():
"""
Create a dataframe widget.
"""
df = get_sys_summits()
return gr.DataFrame(df)


# Define the Gradio interface.
demo = gr.Interface(
fn=get_dataframe,
inputs=[],
outputs=["dataframe"],
title="Gradio with CrateDB Example",
live=True,
allow_flagging="never",
)


if __name__ == "__main__":
demo.launch()
12 changes: 12 additions & 0 deletions framework/gradio/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[tool.pytest.ini_options]
minversion = "2.0"
addopts = """
-rfEX -p pytester --strict-markers --verbosity=3
--capture=no
"""
log_level = "DEBUG"
log_cli_level = "DEBUG"
testpaths = ["*.py"]
xfail_strict = true
markers = [
]
2 changes: 2 additions & 0 deletions framework/gradio/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gradio-client==1.0.*
pytest<9
2 changes: 2 additions & 0 deletions framework/gradio/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gradio==4.*
sqlalchemy-cratedb==0.38.0
84 changes: 84 additions & 0 deletions framework/gradio/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import logging
import shlex
import subprocess
import sys
import threading
import time
from urllib.error import HTTPError
from urllib.request import urlopen

import pytest
from gradio_client import Client


logger = logging.getLogger()

GRADIO_SERVER_PORT = "7861"
GRADIO_API_URL = f"http://localhost:{GRADIO_SERVER_PORT}"


def run(command: str, env=None, timeout: int = None):
"""
Invoke a command in a subprocess.
"""
env = env or {}
env["PATH"] = ""
subprocess.call(shlex.split(command), env=env, timeout=timeout)


def check_url(url):
"""
Check if a service is reachable.

Makes a simple GET request to path of the HTTP endpoint. Service is
available if returned status code is < 500.
"""
try:
r = urlopen(url)
return r.code < 500
except HTTPError as e:
# If service returns e.g. a 404 it's ok
return e.code < 500
except Exception:
# Possible service not yet started
return False


@pytest.fixture(scope="session", autouse=True)
def run_server():

def server_thread():
print("sys.exec:", sys.executable)
try:
run(f"{sys.executable} basic_sys_summits.py", env={"GRADIO_SERVER_PORT": GRADIO_SERVER_PORT}, timeout=5)
except subprocess.TimeoutExpired:
pass

thread = threading.Thread(target=server_thread)
try:
thread.start()
except KeyboardInterrupt:
pass

while not check_url(GRADIO_API_URL):
logger.info("Waiting for Gradio API to come up")
time.sleep(0.2)


def test_read_sys_summits():
"""
Verify reading CrateDB's built-in `sys.summits` database table through the Gradio API.

- https://www.gradio.app/guides/getting-started-with-the-python-client
- https://www.gradio.app/docs/python-client/
- https://www.gradio.app/docs/gradio/dataframe
"""

# Connect to Gradio API, and submit a request.
client = Client(GRADIO_API_URL)
result = client.predict(api_name="/predict")

# Verify result.
assert "mountain" in result["value"]["headers"]
assert len(result["value"]["data"]) > 80
assert result["value"]["data"][0][5] == "Mont Blanc"