Skip to content

Commit 8f5188d

Browse files
authored
Add Jump Start example stock-api-flask (#67)
* Add Jump Start example stock-api-flask * Python requires version should match readme, bump app version for release * Add minimumConnectVersion
1 parent a7ec4e7 commit 8f5188d

File tree

6 files changed

+12973
-0
lines changed

6 files changed

+12973
-0
lines changed

.github/workflows/extensions.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
quarto-stock-report-python: extensions/quarto-stock-report-python/**
3535
portfolio-dashboard: extensions/portfolio-dashboard/**
3636
quarto-document: extensions/quarto-document/**
37+
stock-api-flask: extensions/stock-api-flask/**
3738
landing-page: extensions/landing-page/**
3839
stock-api-fastapi: extensions/stock-api-fastapi/**
3940
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Flask Stock Pricing Service
2+
3+
## About this example
4+
5+
An API allows you to turn your models into production services that other tools and teams can use. APIs are a great way for software engineering teams to use your models without translating them into different languages.
6+
7+
8+
## Learn more
9+
10+
* [Flask Introduction](https://palletsprojects.com/p/flask/)
11+
* [Flask Documentation](https://flask.palletsprojects.com/en/1.1.x/)
12+
13+
## Requirements
14+
15+
* Posit Connect license allows API publishing
16+
* Python version 3.10 or higher

extensions/stock-api-flask/app.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# -*- coding: utf-8 -*-
2+
import os
3+
import numpy as np
4+
import pandas as pd
5+
from flask import Flask
6+
from flask_restx import Api, Resource, fields
7+
8+
# Fetch prices from local CSV using pandas
9+
prices = pd.read_csv(
10+
os.path.join(os.path.dirname(__file__), "prices.csv"),
11+
index_col=0,
12+
parse_dates=True,
13+
)
14+
15+
16+
# Configure the Flask app using RestX for swagger documentation
17+
app = Flask(__name__)
18+
app.config["SWAGGER_UI_DOC_EXPANSION"] = "list"
19+
app.config["RESTX_MASK_SWAGGER"] = False
20+
app.config["ERROR_INCLUDE_MESSAGE"] = False
21+
22+
23+
api = Api(
24+
app,
25+
version="0.1.0",
26+
title="Stocks API",
27+
description="The Stocks API provides pricing and volatility data for a "
28+
"limited number of US equities from 2010-2018",
29+
)
30+
ns = api.namespace("stocks")
31+
32+
# Define stock and price models for marshalling and documenting response objects
33+
tickers_model = ns.model(
34+
"Tickers",
35+
{
36+
"tickers": fields.List(
37+
fields.String(description="Ticker of the stock"),
38+
description="All available stock tickers",
39+
),
40+
},
41+
)
42+
43+
stock_model = ns.model(
44+
"Stock",
45+
{
46+
"ticker": fields.String(description="Ticker of the stock"),
47+
"price": fields.Float(description="Latest price of the stock"),
48+
"volatility": fields.Float(description="Latest volatility of the stock price"),
49+
},
50+
)
51+
52+
price_model = ns.model(
53+
"Price",
54+
{
55+
"date": fields.Date,
56+
"high": fields.Float(description="High price for this date"),
57+
"low": fields.Float(description="Low price for this date"),
58+
"close": fields.Float(description="Closing price for this date"),
59+
"volume": fields.Integer(description="Daily volume for this date"),
60+
"adjusted": fields.Float(description="Split-adjusted price for this date"),
61+
},
62+
)
63+
64+
65+
class TickerNotFound(Exception):
66+
def __init__(self, ticker):
67+
self.ticker = ticker
68+
self.message = "Ticker `{}` not found".format(self.ticker)
69+
70+
def __str__(self):
71+
return "TickerNotFound('{}')".format(self.ticker)
72+
73+
74+
# Our simple API only has a few GET endpoints
75+
@ns.route("/")
76+
class StockList(Resource):
77+
"""Shows a list of all available tickers"""
78+
79+
@ns.marshal_with(tickers_model)
80+
def get(self):
81+
tickers = prices["ticker"].unique()
82+
return {"tickers": tickers}
83+
84+
85+
@ns.route("/<string:ticker>")
86+
@ns.response(404, "Ticker not found")
87+
@ns.param("ticker", "The ticker for the stock")
88+
class Stock(Resource):
89+
"""Shows the latest price and volatility for the specified stock"""
90+
91+
@ns.marshal_list_with(stock_model)
92+
def get(self, ticker):
93+
if ticker not in prices["ticker"].unique():
94+
raise TickerNotFound(ticker)
95+
96+
latest = prices.last_valid_index()
97+
ticker_prices = prices[prices["ticker"] == ticker]
98+
current_price = ticker_prices["close"][latest:].iloc[0].round(2)
99+
current_volatility = np.log(
100+
ticker_prices["adjusted"] / ticker_prices["adjusted"].shift(1)
101+
).var()
102+
103+
return {
104+
"ticker": ticker,
105+
"price": current_price,
106+
"volatility": current_volatility,
107+
}
108+
109+
110+
@ns.route("/<string:ticker>/history")
111+
@ns.response(404, "Ticker not found")
112+
@ns.param("ticker", "The ticker for the stock")
113+
class StockHistory(Resource):
114+
"""Shows the price history for the specified stock"""
115+
116+
@ns.marshal_list_with(price_model)
117+
def get(self, ticker):
118+
if ticker not in prices["ticker"].unique():
119+
raise TickerNotFound(ticker)
120+
121+
ticker_prices = prices[prices["ticker"] == ticker].copy()
122+
ticker_prices.loc[:, "date"] = ticker_prices.index.to_numpy()
123+
return ticker_prices.to_dict("records")
124+
125+
126+
@api.errorhandler(TickerNotFound)
127+
def handle_ticker_not_found(error):
128+
return {"message": error.message}, 404
129+
130+
131+
if __name__ == "__main__":
132+
app.run(debug=True)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"version": 1,
3+
"locale": "en_US.UTF-8",
4+
"metadata": {
5+
"appmode": "python-api",
6+
"entrypoint": "app"
7+
},
8+
"extension": {
9+
"name": "stock-api-flask",
10+
"title": "Flask Stock Pricing Service",
11+
"description": "APIs are a great way for software engineering teams to use your models without translating them into different languages",
12+
"homepage": "https://github.com/posit-dev/connect-extensions/tree/main/extensions/stock-api-flask",
13+
"tags": [],
14+
"minimumConnectVersion": "2025.04.0",
15+
"version": "1.0.0"
16+
},
17+
"environment": {
18+
"python": {
19+
"requires": ">=3.10, <4"
20+
}
21+
},
22+
"python": {
23+
"version": "3.11.7",
24+
"package_manager": {
25+
"name": "pip",
26+
"version": "24.2",
27+
"package_file": "requirements.txt"
28+
}
29+
},
30+
"files": {
31+
"requirements.txt": {
32+
"checksum": "2ef28be9f43365cbce750d40902ed086"
33+
},
34+
"README.md": {
35+
"checksum": "aa9afd650003a1220c22340a26206ea5"
36+
},
37+
"app.py": {
38+
"checksum": "2750567fcf1a81c8371f7fb1ed2d7f9f"
39+
},
40+
"prices.csv": {
41+
"checksum": "e0bc27e3dd358c360863807e09079985"
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)