---
# System prepended metadata

title: 2024/12/27 API

---

# 2024/12/27 API

https://hackmd.io/kTmJqeYLSfCtruduc2eDQA


## Q & A
1. 老師可以再說一次restful Api嗎，謝謝 (Ｘ)
2. ac890831@gmail.com (X)
    - ![image](https://hackmd.io/_uploads/r16u1Cor1e.png) (X)




## 大綱
- 描述API基礎知識
- Yahoo finance API串接
- 基本API設計

## 需預先下載的軟體
- [Vscode](https://code.visualstudio.com/download)
- [Postman](https://www.postman.com/downloads/)
- Python3.10
    - python-dotenv==1.0.1
    - requests==2.31.0
    - Flask-JWT-Extended==4.4.1
    - Flask==2.1.2
    - Flask-RESTful == 0.3.9
    - PyMySQL==1.0.3
    - Werkzeug==2.2.2
- MySQLDB server, 可以用自己習慣的方式下載, 也可以使用 [MAMP](https://www.mamp.info/en/downloads/) 來驅動



## 上課會用到網頁
- [Yahoo finace API](https://financeapi.net/dashboard)
    - 請預先申請好一組 token 方便上課直接使用
- [ChatGPT](https://chat.openai.com/)
- [Fake API website](https://jsonplaceholder.typicode.com/)
- [小測驗](https://docs.google.com/forms/d/196mE1lY7K_99ai8cQoqbMJBilQk0ky_E4AtpfssmwI8/edit) 
- [網頁提供的爬蟲規範](https://{origin}/robots.txt)
## 上半堂

串接 yahoo finance API 題目
```
import requests

url = "https://yfapi.net/v8/finance/spark"
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/spark"這隻API
取得AAPL, TSLA為期一個月且間格為一天的股價, 並整理成下方資料格式
{
    "AAPL": {
        "2021/12/21": "216.5",
        "2021/12/21": "216.5",
        "2021/12/21": "216.5",
        "2021/12/21": "216.5"
    },
    "TSLA": {
        "2021/12/21": "216.5",
        "2021/12/21": "216.5",
        "2021/12/21": "216.5",
        "2021/12/21": "216.5"
    }
}
"""

"""
Steps:
1. 將timestamp轉換成datetime(2021/12/21)
2. 將轉換過的timestamp_list 與close的price list合成上放的dict
3. 將股票代號設為key, value為整理過後的dict並return.
"""
if __name__ == "__main__":
    print(response.json())
```

串接 yahoo finance API 解法
```
import requests
from datetime import datetime


def get_symbols_info(symbols: str) -> dict:
    headers = {"x-api-key": "XeLhSFyv1X235xXISwu3q8mKTDYGqphI7Z8JOts9"}

    url = "https://yfapi.net/v8/finance/spark"
    querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
    response = requests.request("GET", url, headers=headers, params=querystring)
    data = response.json()
    # Before
    """
    data = {
        'MSFT': {
            'chartPreviousClose': 301.37, 
            'timestamp': 
                [1649424600, 1649683800, 1649770200, 1649856600, 1649943899], 
            'symbol': 'MSFT', 
            'previousClose': None, 
            'close': [296.97, 285.26, 282.06, 287.62, 286.05], 
            'end': None, 
            'start': None, 
            'dataGranularity': 300}, 
            'AAPL': 
                {
                    'chartPreviousClose': 172.14, 
                    'timestamp': 
                        [1649424600, 1649683800, 1649770200, 1649856600, 1649943898], 
                    'symbol': 'AAPL', 
                    'previousClose': None, 
                    'close': 
                        [170.09, 165.75, 167.66, 170.4, 169.65], 
                    'end': None, 
                    'start': None, 
                    'dataGranularity': 300
                }
            }
        }
    """

    # After
    """
    {
        "AAPL": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        },
        "TSLA": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        }
    }
    """
    results = {}

    for symbol in symbols.split(","):
        # Convert timestamp to str datetime
        timestamps = data[symbol]["timestamp"]
        timestamps = [
            datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
            for timestamp in timestamps
        ]

        close_piece = data[symbol]["close"]
        # Create dict with two list
        symbol_info_mapping = dict(zip(timestamps, close_piece))
        results[symbol] = symbol_info_mapping
    return results


if __name__ == "__main__":
    print(get_symbols_info("AAPL,MSFT"))

```

## 下半堂

在 window 中開啟一個 python 的虛擬環境
到官網下載 python 切記，把加入 path 的勾勾勾起來
```
1. pip3 install virtualenv 

2. Set-ExecutionPolicy Unrestricted -Force 

    2.1 將 vscode 以系統管理員的身份執行（內容 -> 相容性）

3. virtualenv venv (創建 venv)

4. .\venv\Scripts\activate (進入 venv)

5. deactivate (退出 venv)


Reference
- https://stackoverflow.com/questions/18713086/virtualenv-wont-activate-on-windows
- https://takenoterecalllater.wordpress.com/2021/07/07/%E5%A6%82%E4%BD%95%E5%9C%A8-win10-%E8%AE%93-visual-studio-code-%E7%9A%84-terminal-%E4%BB%A5-%E7%B3%BB%E7%B5%B1%E7%AE%A1%E7%90%86%E5%93%A1%E8%BA%AB%E5%88%86%E5%9F%B7%E8%A1%8C/
```


api_basic/api.py
Flask 官網: https://flask.palletsprojects.com/en/stable/quickstart/
下載所有 requirements.txt 的 package`pip install -r requirements.txt`

```
from flask import Flask
import requests
from datetime import datetime


def get_symbols_info(symbols: str) -> dict:
    headers = {"x-api-key": ""}

    url = "https://yfapi.net/v8/finance/spark"
    querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
    response = requests.request("GET", url, headers=headers, params=querystring)
    data = response.json()
    # Before
    """
    data = {
        'MSFT': {
            'chartPreviousClose': 301.37, 
            'timestamp': 
                [1649424600, 1649683800, 1649770200, 1649856600, 1649943899], 
            'symbol': 'MSFT', 
            'previousClose': None, 
            'close': [296.97, 285.26, 282.06, 287.62, 286.05], 
            'end': None, 
            'start': None, 
            'dataGranularity': 300}, 
            'AAPL': 
                {
                    'chartPreviousClose': 172.14, 
                    'timestamp': 
                        [1649424600, 1649683800, 1649770200, 1649856600, 1649943898], 
                    'symbol': 'AAPL', 
                    'previousClose': None, 
                    'close': 
                        [170.09, 165.75, 167.66, 170.4, 169.65], 
                    'end': None, 
                    'start': None, 
                    'dataGranularity': 300
                }
            }
        }
    """

    # After
    """
    {
        "AAPL": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        },
        "TSLA": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        }
    }
    """
    results = {}

    for symbol in symbols.split(","):
        # Convert timestamp to str datetime
        timestamps = data[symbol]["timestamp"]
        timestamps = [
            datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
            for timestamp in timestamps
        ]

        close_piece = data[symbol]["close"]
        # Create dict with two list
        symbol_info_mapping = dict(zip(timestamps, close_piece))
        results[symbol] = symbol_info_mapping
    return results


app = Flask(__name__)


@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"


@app.route("/stock")
def stock():
    return get_symbols_info("AAPL,MSFT")


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=10009, use_reloader=True)
```


api_rest_basic/api.py
解析帶入參數的方式: https://flask-restful.readthedocs.io/en/latest/quickstart.html#a-minimal-api
![image](https://hackmd.io/_uploads/BJbs-JnBJg.png)


定義從哪裡取得參數: https://flask-restful.readthedocs.io/en/latest/reqparse.html
![image](https://hackmd.io/_uploads/HJGc-y3Skx.png)


資料夾路徑參考
![image](https://hackmd.io/_uploads/rJu7z12S1l.png)


api_rest_basic/api.py
```
from flask import Flask
from flask_restful import Api

from resource.stock import Stock

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


api.add_resource(Stock, "/stock")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port="10009", debug=True, use_reloader=True)

```

api_rest_basic/resource/stock.py
記得加上自己的 x-api-key
```
import requests
from datetime import datetime

from flask_restful import Resource
from flask_restful import reqparse


def get_symbols_info(symbols: str) -> dict:
    headers = {"x-api-key": ""}

    url = "https://yfapi.net/v8/finance/spark"
    querystring = {"symbols": symbols, "range": "5d", "interval": "1d"}
    response = requests.request("GET", url, headers=headers, params=querystring)
    data = response.json()
    # Before
    """
    data = {
        'MSFT': {
            'chartPreviousClose': 301.37, 
            'timestamp': 
                [1649424600, 1649683800, 1649770200, 1649856600, 1649943899], 
            'symbol': 'MSFT', 
            'previousClose': None, 
            'close': [296.97, 285.26, 282.06, 287.62, 286.05], 
            'end': None, 
            'start': None, 
            'dataGranularity': 300}, 
            'AAPL': 
                {
                    'chartPreviousClose': 172.14, 
                    'timestamp': 
                        [1649424600, 1649683800, 1649770200, 1649856600, 1649943898], 
                    'symbol': 'AAPL', 
                    'previousClose': None, 
                    'close': 
                        [170.09, 165.75, 167.66, 170.4, 169.65], 
                    'end': None, 
                    'start': None, 
                    'dataGranularity': 300
                }
            }
        }
    """

    # After
    """
    {
        "AAPL": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        },
        "TSLA": {
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5",
            "2021/12/21": "216.5"
        }
    }
    """
    results = {}

    for symbol in symbols.split(","):
        # Convert timestamp to str datetime
        timestamps = data[symbol]["timestamp"]
        timestamps = [
            datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d")
            for timestamp in timestamps
        ]

        close_piece = data[symbol]["close"]
        # Create dict with two list
        symbol_info_mapping = dict(zip(timestamps, close_piece))
        results[symbol] = symbol_info_mapping
    return results


class Stock(Resource):
    def get(self):
        parser = reqparse.RequestParser()
        parser.add_argument(
            "symbols",
            type=str,
            location="args",
        )
        args = parser.parse_args()
        symbols = args.get("symbols")
        return get_symbols_info(symbols)

```

結果呈現
![image](https://hackmd.io/_uploads/SyvJrJ3rJl.png)

---
接下來的練習
創建一個新的 API /user 
可以透過 POST 的方式去新增一筆資料進去





### Step1.
建立 DB
![image](https://hackmd.io/_uploads/SyT_iyhSJx.png)
長度隨意，不要太短就好
table(user)
- id: (AUTO_INCREMENT)
- name: str
- gender: str
- birth: str
- note: str


### DB 連線程式碼參考
與 MySQL DB 建立連線
```
import pymysql


def db_init():
    db = pymysql.connect(
        host="127.0.0.1", user="root", password="root", port=8889, db="api_class"
    )
    cursor = db.cursor(pymysql.cursors.DictCursor)
    return db, cursor
```

新增資料 SQL 與語法參考
```
INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
        VALUES ('{}','{}','{}','{}');
```


POST parser.add_argument 的 location
![image](https://hackmd.io/_uploads/SJDGgghr1e.png)


POST /user API 程式碼參考
```
db, cursor = db_init()
sql = """

INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
VALUES ('{}','{}','{}','{}');

""".format(
    user["name"], user["gender"], user["birth"], user["note"]
)
result = cursor.execute(sql)
message = "success" if result == 1 else "failure"
db.commit()
db.close()
```


GET /user API
```
from flask import jsonify

class Users(Resource):
    def get(self):
        db, cursor = db_init()
        
        sql = "SELECT * FROM api_class.user;"

        cursor.execute(sql)
        users = cursor.fetchall()
        db.close()
        return jsonify(users)
```

api_rest_basic/resource/user.py
程式碼參考，請自行修正
```
import json
from flask import jsonify
from flask_restful import Resource, reqparse
import pymysql


def db_init():
    db = pymysql.connect(
        host="127.0.0.1", user="root", password="root", port=8889, db="api_class"
    )
    cursor = db.cursor(pymysql.cursors.DictCursor)
    return db, cursor


class Users(Resource):
    def get(self):
        db, cursor = db_init()
        
        sql = "SELECT * FROM api_class.user;"

        cursor.execute(sql)
        users = cursor.fetchall()
        db.close()
        return jsonify(users)

    # Create User
    def post(self):
        db, cursor = db_init()
        sql = """

        INSERT INTO `api_class`.`user` (`name`,`gender`,`birth`,`note`)
        VALUES ('{}','{}','{}','{}');

        """.format(
            user["name"], user["gender"], user["birth"], user["note"]
        )
        result = cursor.execute(sql)
        message = "success" if result == 1 else "failure"
        db.commit()
        db.close()

        return {"message": message}

```

Postman 驗證
新增
![image](https://hackmd.io/_uploads/S13LelnSkx.png)
取得
![image](https://hackmd.io/_uploads/HytjrghrJg.png)

