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

CLI Tests: Deploy all Python Content Types #816

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 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
3 changes: 2 additions & 1 deletion .github/workflows/contract.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ jobs:
- run: echo "${FUZZBUCKET_SSH_KEY}" > test/cy/fuzzbucket-ssh-key && chmod 600 test/cy/fuzzbucket-ssh-key
- run: just cy install
- run: just cy test
- run: just cy test-contract
# disable cypress contract tests until new deploy workflow
# - run: just cy test-contract
- run: just bats install
- run: just bats test
- run: just bats test-contract
Expand Down
22 changes: 22 additions & 0 deletions test/bats/contract/accounts.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bats

load '../node_modules/bats-support/load'
load '../node_modules/bats-assert/load'

# list-accounts should return the account from env
@test "list accounts" {
run ${EXE} list-accounts
assert_success
assert_output --partial "Nickname: \"env\""
assert_output --partial "Configured via: CONNECT_SERVER environment variable"
assert_output --partial "Authentication: Connect API key"
}

# test-account should pass with env
@test "test accounts" {
run ${EXE} test-account env
assert_success
assert_output --partial "Name: Administrator Smith"
assert_output --partial "Username: admin"
assert_output --partial "Email: [email protected]"
}
36 changes: 12 additions & 24 deletions test/bats/contract/deploy.bats
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,11 @@

load '../node_modules/bats-support/load'
load '../node_modules/bats-assert/load'

# list-accounts should return the account from env
@test "list accounts" {
run ${EXE} list-accounts
assert_success
assert_output --partial "Nickname: \"env\""
assert_output --partial "Configured via: CONNECT_SERVER environment variable"
assert_output --partial "Authentication: Connect API key"
}

# test-account should pass with env
@test "test accounts" {
run ${EXE} test-account env
assert_success
assert_output --partial "Name: Administrator Smith"
assert_output --partial "Username: admin"
assert_output --partial "Email: [email protected]"
}
source ../sample-content/python/${CONTENT}/.env

# deploy content with the env account
@test "deploy content" {
run ${EXE} deploy ../sample-content/fastapi-simple/ -n ci_deploy
@test "deploy ${CONTENT}" {
run ${EXE} deploy ../sample-content/python/${CONTENT} -n ci_deploy
assert_success
assert_output --partial "Test Deployment... [OK]"
# now test the deployment via api
Expand All @@ -33,12 +16,12 @@ load '../node_modules/bats-assert/load'
run curl --silent --show-error -L --max-redirs 0 --fail \
-X GET \
-H "Authorization: Key ${CONNECT_API_KEY}" "${CONNECT_SERVER}/__api__/v1/content/${GUID}"
assert_output --partial "\"app_mode\":\"python-fastapi\""
assert_output --partial "\"app_mode\":\"${CONTENT_TYPE}\""
}

# redeploy content from previous test
@test "redeploy content" {
run ${EXE} redeploy ci_deploy ../sample-content/fastapi-simple/
@test "redeploy ${CONTENT}" {
run ${EXE} redeploy ci_deploy ../sample-content/python/${CONTENT}
assert_success
assert_output --partial "Test Deployment... [OK]"
# now test the deployment via api
Expand All @@ -48,6 +31,11 @@ load '../node_modules/bats-assert/load'
run curl --silent --show-error -L --max-redirs 0 --fail \
-X GET \
-H "Authorization: Key ${CONNECT_API_KEY}" "${CONNECT_SERVER}/__api__/v1/content/${GUID}"
assert_output --partial "\"app_mode\":\"python-fastapi\""
assert_output --partial "\"app_mode\":\"${CONTENT_TYPE}\""
# cleanup
run rm -rf ../sample-content/python/${CONTENT}/.posit/
}

teardown_file() {
rm -rf ../sample-content/python/${CONTENT}/.posit
}
18 changes: 16 additions & 2 deletions test/bats/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ alias c := clean
alias i := install
alias t := test

export CONTENT := env_var_or_default("CONTENT", "all")

_ci := env_var_or_default("CI", "false")

_debug := env_var_or_default("DEBUG", "false")
Expand Down Expand Up @@ -61,8 +63,20 @@ test-contract:
pip install -r ../setup/requirements.txt
export CONNECT_SERVER="$(python ../setup/connect_setup.py)"
export CONNECT_API_KEY="$(python ../setup/gen_apikey.py 'admin')"

EXE=$exe npm run contract
echo "CONTENT: ${CONTENT}"
EXE=$exe npm run accounts
if [[ "${CONTENT}" == "all" ]]; then
content_list=$(python ../deploy_helper.py)
for i in ${content_list}
do
export CONTENT=${i}
EXE=$exe npm run deploy
done
else
EXE=$exe npm run deploy
fi



# Executes bats local tests
test:
Expand Down
3 changes: 2 additions & 1 deletion test/bats/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"private": true,
"scripts": {
"contract": "bats -r -T -t ./contract",
"deploy": "bats -r -T -t ./contract/deploy.bats",
"accounts": "bats -r -T -t ./contract/accounts.bats",
"cli": "bats -r -T -t ./cli"
},
"devDependencies": {
Expand Down
10 changes: 10 additions & 0 deletions test/deploy_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os

# Specify the directory path
directory_path = "../sample-content/python/"

# Get a list of directories
subdirectories = [d for d in os.listdir(directory_path) if os.path.isdir(os.path.join(directory_path, d))]

# Print the list of directories
print(str(subdirectories).replace("[","").replace("]","").replace("'","").replace(",",""))
1 change: 1 addition & 0 deletions test/sample-content/python/fastapitableau-example/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-fastapi
19 changes: 19 additions & 0 deletions test/sample-content/python/fastapitableau-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Tableau Python Extension

## About this example

fastapitableau is a Python package that enables Python developers to build FastAPI APIs that function as Tableau Analytics Extensions. These extensions can be leveraged from Tableau workbooks to allow real time requests from Tableau to Python. This extension builds on top of Tableau's example Superstore dataset.


## Learn more

* [Learn how to use this extension from Tableau](https://github.com/sol-eng/tableau-examples/tree/main/superstore)
* [Documentation for fastapitableau](https://github.com/rstudio/fastapitableau)
* [Configuring Posit Connect for use with Tableau](https://docs.posit.co/connect/admin/integrations/tableau/)

## Requirements

* Posit Connect license allows API publishing
* Python version 3.7 or higher

<!-- NOTE: this file is generated -->
42 changes: 42 additions & 0 deletions test/sample-content/python/fastapitableau-example/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from fastapitableau import FastAPITableau
from joblib import load
import pandas as pd
from typing import List

# Load model
model = load("model.joblib")

# Define model_pipline
def model_pipeline(dict):
model_data = pd.DataFrame(dict)
model_data["ship_diff"] = (
model_data.days_to_ship_actual - model_data.days_to_ship_scheduled
)
pred_columns = ["ship_diff", "quantity", "sales", "discount"]
return model.predict(model_data.loc[:, pred_columns]).tolist()


# Define the extension
app = FastAPITableau(
title="Predicted Profit",
description="A simple linear prediction of sales profit given new input data",
version="0.1.0",
)


@app.post("/predict")
async def predict(
days_to_ship_actual: List[int],
days_to_ship_scheduled: List[int],
quantity: List[int],
sales: List[float],
discount: List[float],
) -> List[float]:
data = {
"days_to_ship_actual": days_to_ship_actual,
"days_to_ship_scheduled": days_to_ship_scheduled,
"quantity": quantity,
"sales": sales,
"discount": discount,
}
return model_pipeline(data)
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fastapitableau
joblib
pandas
scikit-learn
typing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/sample-content/python/python-falcon-asgi/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-fastapi
18 changes: 18 additions & 0 deletions test/sample-content/python/python-falcon-asgi/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from falcon.asgi import App


class PingResource:
async def on_get(self, req, resp):
data = {
"headers": dict(req.headers),
"environ": dict(os.environ),
"link": req.relative_uri,
}

resp.media = data


app = App()
app.add_route("/ping", PingResource())
# app.add_route("/", PingResource())
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
falcon
1 change: 1 addition & 0 deletions test/sample-content/python/python-falcon/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-api
17 changes: 17 additions & 0 deletions test/sample-content/python/python-falcon/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os
import falcon


class PingResource:
def on_get(self, req, resp):
data = {
"headers": dict(req.headers),
"environ": dict(os.environ),
"link": req.relative_uri,
}

resp.media = data


app = falcon.API()
app.add_route("/ping", PingResource())
1 change: 1 addition & 0 deletions test/sample-content/python/python-falcon/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
falcon
1 change: 1 addition & 0 deletions test/sample-content/python/python-flasgger/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-api
2 changes: 2 additions & 0 deletions test/sample-content/python/python-flasgger/.positignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
README.md
blue.txt
Empty file.
18 changes: 18 additions & 0 deletions test/sample-content/python/python-flasgger/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
from flask import Flask, jsonify
from flasgger import Swagger

app = Flask(__name__)
app.config["SWAGGER"] = {"title": "Greetings API"}
db = json.load(open("greetings.json"))
Swagger(app)


@app.route("/greetings/")
def list():
return jsonify([{"lang": lang, "text": text} for lang, text in sorted(db.items())])


@app.route("/greetings/<lang>/")
def get(lang):
return jsonify({"lang": lang, "text": db.get(lang)})
Empty file.
9 changes: 9 additions & 0 deletions test/sample-content/python/python-flasgger/greetings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ar": "آلو",
"bn": "হ্যালো",
"chr": "ᏏᏲ",
"en": "Hello",
"es": "Hola",
"sw": "هَبَارِ",
"zh": "你好"
}
5 changes: 5 additions & 0 deletions test/sample-content/python/python-flasgger/pyvenv.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
home = /Users/kgartland/.pyenv/versions/3.11.6/bin
include-system-site-packages = false
version = 3.11.6
executable = /Users/kgartland/.pyenv/versions/3.11.6/bin/python3.11
command = /Users/kgartland/.pyenv/versions/3.11.6/bin/python -m venv /Users/kgartland/work/connect/test/rsconnect-python/bundles/python-flasgger
2 changes: 2 additions & 0 deletions test/sample-content/python/python-flasgger/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask
flasgger
1 change: 1 addition & 0 deletions test/sample-content/python/python-flask-restful/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-api
23 changes: 23 additions & 0 deletions test/sample-content/python/python-flask-restful/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os

from flask import Flask, request, url_for
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
def get(self):
return {
"headers": dict(request.headers),
"environ": dict(os.environ),
"link": url_for("hello"),
"external_link": url_for("hello", _external=True),
}


api.add_resource(HelloWorld, "/", endpoint="hello")

if __name__ == "__main__":
app.run(debug=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flask-restful
1 change: 1 addition & 0 deletions test/sample-content/python/python-flask-slow/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-api
26 changes: 26 additions & 0 deletions test/sample-content/python/python-flask-slow/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
import time
from flask import Flask, jsonify, request

app = Flask(__name__)
count = 0


@app.route("/")
def index():
request_id = request.args.get("id")
delay = int(request.args.get("delay", "0"))
start_time = time.time()
while time.time() - start_time < delay:
pass

global count
count += 1

return jsonify(
dict(id=request_id, count=count, pid=os.getpid(), timestamp=int(time.time()))
)


if __name__ == "__main__":
app.run()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flask
1 change: 1 addition & 0 deletions test/sample-content/python/python-sanic/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONTENT_TYPE=python-fastapi
18 changes: 18 additions & 0 deletions test/sample-content/python/python-sanic/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from sanic import Sanic
from sanic.response import json

app = Sanic(__name__)


@app.route("/ping")
async def ping(request):
data = {
"headers": dict(request.headers),
"environ": dict(os.environ),
}
return json(data)


if __name__ == "__main__":
app.run()
1 change: 1 addition & 0 deletions test/sample-content/python/python-sanic/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sanic
Loading