# 2024/12/28 API
## Q & A
1. 完整 API 專案已寄出,若有問題再跟我說!
## 大綱
- Python 語法說明(decorator)
- 基本API設計(User All)
- 進階API設計
- Swagger文件
- JWT token
## 推薦課程
資料結構與演算法
- CS61B: https://sp23.datastructur.es/ (網路上搜尋一下會加上中文字幕的版本)
- 台大線上課程:https://www.youtube.com/playlist?list=PLMHSr8fseBzV7CHWQUXDGRPFkhKlbJjvj
## 需預先下載的軟體
- [Vscode](https://code.visualstudio.com/download)
- [Postman](https://www.postman.com/downloads/)
- Python3.10
```
aniso8601==9.0.1
apispec==5.2.2
certifi==2023.5.7
charset-normalizer==3.2.0
click==8.1.3
Flask==2.1.2
flask-apispec==0.11.4
Flask-JWT-Extended==4.4.1
Flask-RESTful==0.3.9
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
marshmallow==3.18.0
marshmallow-enum==1.5.1
packaging==24.2
pip==24.3.1
PyJWT==2.6.0
python-dotenv==1.0.1
pytz==2022.1
requests==2.31.0
setuptools==75.6.0
six==1.16.0
urllib3==1.26.16
webargs==8.2.0
Werkzeug==2.2.2
```
- ~~MySQLDB server, 可以用自己習慣的方式下載, 也可以使用 [MAMP](https://www.mamp.info/en/downloads/) 來驅動~~
## 上課會用到網頁
- [ChatGPT](https://chat.openai.com/)
- [Jwt解析網頁](https://jwt.io/)
- [Marshmello官網](https://marshmallow.readthedocs.io/en/stable/)
## Mac 的 virtualenv 的方式
```
pip3 install virtualenv
virtualenv venv
source venv/bin/activate
deactivate
```

## 上半堂
### vscode 快捷鍵
- 連續按 crtl + D: 持續選取相同的字元, 直到沒有符合條件為止,
好處是可以一次修改相同的字元。

- alt + shift + 下 / 上: 複製鼠標所在的行(上:往上新增,下:往下新增)
- ctrl + P: 透過檔案名稱,搜尋檔案。

- ctrl + F: 該檔案全域搜尋符合條件的字元。

- alt + 上 / 下:移動鼠標所在的行(上:往上移動,下:往下移動)
### 裝飾器(decorator)
```
# Introduction
# function的加工 (紀錄資訊)
def execute():
print("nice")
def print_func_name(func):
print(f"The function name is {func.__name__}")
func()
if __name__ == "__main__":
print_func_name(execute)
```
```
## function的加工2
def execute():
print("nice")
def print_func_name(func):
def warp():
print(f"The function name is {func.__name__}")
func()
return warp
if __name__ == "__main__":
print_func_name(execute)()
```
```
# Basic decorator
def print_func_name(func):
def warp():
print(f"The function name is {func.__name__}")
func()
return warp
@print_func_name
def execute():
print("nice")
if __name__ == "__main__":
execute()
```
```
# Multi decorator
def print_func_hash_code(func):
def warp():
print(f"The function hash code is {func.__hash__()}")
func()
return warp
def print_func_name(func):
def warp():
print(f"The function name is {func.__name__}")
func()
return warp
@print_func_hash_code
@print_func_name
def execute():
print("nice")
if __name__ == "__main__":
execute()
```
```
# Decorator with parameter
def print_func_with_param(param):
def decorator(func):
def wrap():
print(f"The function name is {func.__name__}, parameter is {param}")
func()
return wrap
return decorator
@print_func_with_param("nice")
def execute():
print("Main execute")
if __name__ == "__main__":
execute()
```
練習題
```
"""
在 execute 的 func 中加入驗證的裝飾器, 驗證內容為輸入正確的帳號密碼(input)
帳號密碼自行定義, 要驗證通過才能取response值
"""
def login(func):
def warp():
username, password = input("您的帳號為:"), input("您的密碼為:")
if username == "john" and password == "john":
return func()
else:
print("帳號密碼登入失敗")
return warp
@login
def execute():
print("nice")
if __name__ == "__main__":
execute()
```
結果呈現如下

```
# 解答2:
account = input("帳號:")
password = input("密碼:")
def login(account, password):
def decorator(func):
def acps():
if account == "XXX" and password == "123456":
func()
else:
print("error")
return acps
return decorator
@login(account, password)
def execute():
print("nice")
if __name__ == "__main__":
execute()
```
## User API
==使用習慣==
建議檔案結構

resource(folder): 放所有的 API route,route 的檔名以 route 的名稱為主 e.g., /user -> user.py
- `__init__.py`: 一定要加!!!不然會無法被其他檔案 import
- `user.py`: route 的邏輯檔案
- `user_route_model.py`: route 的序列化 (serializer) 檔案,給 swagger 使用
`api.py`: API 專案的進入點,同時是執行的檔案 `python3 api.py`
`untl.py`: 整個專案會用到的共用 function e.g., timestamp 轉換,格式同一 func...
POSTMAN 結果

解析帶入參數的方式: https://flask-restful.readthedocs.io/en/latest/quickstart.html#a-minimal-api

定義從哪裡取得參數: https://flask-restful.readthedocs.io/en/latest/reqparse.html

resource/user_route_model.py
```
from marshmallow import Schema, fields
# Request
class UserPutSchema(Schema):
name = fields.Str(doc="name", example="string", required=True)
birth = fields.Str(doc="birth", example="string", required=True)
note = fields.Str(doc="note", example="string", required=True)
class UserPostSchema(Schema):
name = fields.Str(doc="name", example="string", required=True)
birth = fields.Str(doc="birth", example="string", required=True)
note = fields.Str(doc="note", example="string", required=True)
# Response
class UserGetResponse(Schema):
message = fields.Str(example="success")
datatime = fields.Str(example="1970-01-01T00:00:00.000000")
data = fields.List(fields.Dict())
class UserSingleGetResponse(Schema):
message = fields.Str(example="success")
datatime = fields.Str(example="1970-01-01T00:00:00.000000")
data = fields.Dict()
class UserPostResponse(Schema):
message = fields.Str(example="success")
class UserPutResponse(Schema):
message = fields.Str(example="success")
class UserSingleDeleteResponse(Schema):
message = fields.Str(example="success")
```
resource/user.py
```
import json
from flask_restful import Resource, reqparse
from flask_apispec import MethodResource, marshal_with, doc, use_kwargs
from . import user_route_model
import util
class Users(MethodResource):
# GET_ALL
@doc(description="Get Users info.", tags=["User"])
@marshal_with(user_route_model.UserGetResponse, code=200)
def get(self):
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
return util.success(data)
# Create User
@doc(description="Get Users info.", tags=["User"])
@use_kwargs(user_route_model.UserPostSchema, location="form")
@marshal_with(user_route_model.UserPostResponse, code=201)
def post(self, name, birth, note):
user = {
"name": name,
"birth": birth,
"note": note,
}
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
data.append(user)
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return util.success(status_code=201)
class User(MethodResource):
# Get single by id
@doc(description="Get Single Users info.", tags=["User"])
@marshal_with(user_route_model.UserSingleGetResponse, code=200)
def get(self, id):
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
if id > len(data):
return util.failure(f"User id {id} not found.", status_code=400)
else:
return util.success(data[id - 1])
@doc(description="Update Single Users info.", tags=["User"])
@use_kwargs(user_route_model.UserPutSchema, location="form")
@marshal_with(user_route_model.UserPutResponse, code=200)
def put(self, id, name, birth, note):
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
if id > len(data):
return util.failure(f"User id {id} not found.", status_code=400)
user = {
"name": name,
"birth": birth,
"note": note,
}
data[id - 1] = user
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return util.success()
@doc(description="Delete Single Users info.", tags=["User"])
@marshal_with(user_route_model.UserSingleDeleteResponse, code=204)
def delete(self, id):
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
if id <= len(data): # 這裡要加 =,才能刪除最後一筆資料
data[id - 1] = {}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return util.success(status_code=204)
```
api.py
```
from flask import Flask
from flask_restful import Api
from apispec import APISpec
from flask_apispec.extension import FlaskApiSpec
from apispec.ext.marshmallow import MarshmallowPlugin
from flask_jwt_extended import JWTManager
from resource.user import Users, User
from resource.login import Login
# Flask init
app = Flask(__name__)
# FlaskRestFul init
api = Api(app)
# Swagger
## JWT swagger setting
### JWT Secret Key 的設定
app.config["JWT_SECRET_KEY"] = "secret_key"
### 為了讓 swagger 能長出鎖頭 (如附件1)
security_definitions = {
"bearer": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
}
}
app.config.update(
{
"APISPEC_SPEC": APISpec(
title="Awesome Projectdfdfssdd",
version="v1",
### 實際上帶入 swagger 的位置
securityDefinitions=security_definitions,
plugins=[MarshmallowPlugin()],
openapi_version="2.0.0",
),
"APISPEC_SWAGGER_URL": "/swagger/", # URI to access API Doc JSON
"APISPEC_SWAGGER_UI_URL": "/swagger-ui/", # URI to access UI of API Doc
}
)
# Swagger init
docs = FlaskApiSpec(app)
# Route
api.add_resource(Users, "/users")
docs.register(Users)
api.add_resource(User, "/user/<int:id>")
docs.register(User)
api.add_resource(Login, "/login")
docs.register(Login)
if __name__ == "__main__":
### JWT 初始化,讓 JWT 可以在 flask 中被使用
jwt = JWTManager().init_app(app)
app.run(host="0.0.0.0", port="10009", debug=True, use_reloader=True)
```
util.py
```
from datetime import datetime
def success(data=None, status_code=200):
if data is None:
return {"message": "success"}, status_code
return {
"message": "success",
"data": data,
"datatime": datetime.utcnow().isoformat(),
}, status_code
def failure(data=None, status_code=500):
if data is None:
return {"message": "failure"}, status_code
return {
"message": "failure",
"data": data,
"datatime": datetime.utcnow().isoformat(),
}, status_code
```
#### JWT 說明
API 加入驗證保護
```
@doc(
description="Delete Single Users info.",
tags=["User"],
security=[{"bearer": []}],
)
@marshal_with(user_route_model.UserSingleDeleteResponse, code=204)
@jwt_required()
def delete(self, id):
with open("user.json", "r", encoding="utf-8") as f:
data = json.load(f)
if id < len(data):
data[id - 1] = {}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return util.success(status_code=204)
```
`security=[{"bearer": []}],`
讓 API 在 Swagger 介面上能自動帶入鎖頭(token)

`@jwt_required()`
加在 API 的 function,讓這 API 被 call 時,需要帶入 JWT token
產出JWT token
```
import json
from flask_jwt_extended import create_access_token
from flask_apispec import MethodResource, marshal_with, doc, use_kwargs
import util
from resource import login_router_model
from datetime import timedelta
class Login(MethodResource):
@doc(description="User Login", tags=["Login"])
@use_kwargs(login_router_model.LoginSchema, location="form")
@marshal_with(login_router_model.LoginResponse, code=200)
def post(self, account, password):
if account == "john" and password == "john":
token = create_access_token(
identity={"account": account}, expires_delta=timedelta(days=1)
)
return util.success(token)
else:
return util.failure("Account or password is wrong")
```
`create_access_token`
透過這個 func 產出 jwt token,裡面需要帶入以下兩個參數
1. identity: 要產出的資訊,此資訊可以被解析出來 e.g., 下圖中的 sub: {account: john}(解析網站如附件2) 
2. expires_delta: 這個 token 多久後過期
### 驗證方式
Swagger 界面驗證
記得最前面要加上 “Bearer”

Postman 驗證
Authorization -> Auth Type -> Bearer Token -> 直接貼上(不須要加上 Bearer,Postman 會自動幫忙帶入)

附件1: 鎖頭

附件2: jwt解析
前往 https://jwt.io/ 這個網頁,將產出的 token 貼上即可解析

---
作業1
```
import requests
url = "https://yfapi.net/v8/finance/chart"
querystring = {"symbols": "AAPL", "interval": "1d", "range": "1mo"}
headers = {"x-api-key": ""}
response = requests.request("GET", url, headers=headers, params=querystring)
results = response.json()
"""
透過 https://yfapi.net/v8/finance/chart/{ticker} 的API
取出AAPL , Tsla, MSFT的 為期5天且間隔為1天的股價資訊(low, high, open, close),
並整理成下放的資料架構
Tips:
1. 把timestamp格式改為datetime並設為key
2. 建立一個dict, 並把股票代號設為key值
3. 取出個股票的特定資料價格, 並把它設為另一個dict的value值
"""
"""
{
"2021/12/21": {
"AAPL" : {"open": 100, "close": 120, "high": 150, "low": 99},
"NFLX" : {"open": 100, "close": 120, "high": 150, "low": 99},
"GOOGL" : {"open": 100, "close": 120, "high": 150, "low": 99},
},
"2021/12/21": {
"AAPL" : {"open": 100, "close": 120, "high": 150, "low": 99},
"NFLX" : {"open": 100, "close": 120, "high": 150, "low": 99},
"GOOGL" : {"open": 100, "close": 120, "high": 150, "low": 99},
},
}
}
"""
if __name__ == "__main__":
print(response.json())
```