API作り

### まとめ未完了　ドキュメ(今日中にやる)

https://codevoweb.com/use-flask-framework-to-build-a-restful-api-in-python/

# これから触りたいAPI

```ruby:
OpenWeatherMap	天気情報	「今日の天気」を表示
🐶 Dog API	犬の画像を取得できる	「クリックでランダム犬画像」
🧠 Advice Slip	ランダムな名言・助言	「元気が出る一言」アプリ
📦 JSONPlaceholder	仮データで練習できる	投稿一覧/新規投稿などのCRUD練習に最適
📅 Holidays API	祝日一覧	カレンダーと組み合わせて使える
```

### ワシが作りたいAPI アイデア
```ruby:
① 投稿→表＋PDF出力	Flask + HTML + pdfkit	
② Ajaxで通知	Ajax + 通知テーブル + .append()	「PDF作ったよ」 通知
③ Notion APIでメモ取得	                        	.append()で表示
④ LINE Messaging API連携	FlaskでWebhook受信・送信	「こんにちは」で返信させる
⑤ Outlook APIでメール取得	MS Graph API + OAuth認証	メール一覧 Ajaxで表示
⑥ メールPDF化＋企業名抽出	pdfkit + reやspaCy	PDF生成して正規表現で企業名抽出＋CSV保存
```

#restful API
使用用途　：フロントをVue/ Ajax /スマホアプリに or チーム開発でフロントとバックをわけたい

仕組み : JSONデータを受け取る　＋受け取ったJsondataの整形。

JsonをSQLAcademyやDBからとってきて加工するには、
必ずMAshmaron 必須。


### MAshmaron使用例

```ruby:
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import fields

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
    first_name = db.Column(db.String(100), default=None)
    last_name = db.Column(db.String(100), default=None)
    created_at = db.Column(db.DateTime, default=datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)

    class UserSchema(ma.SQLAlchemySchema):  
    class Meta:
        model = User
        load_instance = True

    id = ma.auto_field()
    name = ma.auto_field()
    email = ma.auto_field()
    is_active = ma.auto_field()
    first_name = ma.auto_field()
    last_name = ma.auto_field()
    created_at = fields.DateTime(format='%Y-%m-%dT%H:%M:%S+09:00')
    updated_at = fields.DateTime(format='%Y-%m-%dT%H:%M:%S+09:00')
    full_name = fields.Method("get_full_name")

    def get_full_name(self, obj):
        return f"{obj.first_name} {obj.last_name}"

   
   ###出力結果
   {
  "status": "ok",
  "users": [
    {
      "id": 1,
      "full_name": "yuto matsuki",
      "created_at": "2017-08-19T19:50:39+00:00",
      "updated_at": "2017-08-19T19:50:39+00:00"
    },
    {
      ...
    }
    ...
  ]
}

 ### marshmallowでModelSchemaを定義
 class UserSchema(ma.SQLAlchemySchema):  
    class Meta:
        model = User
        load_instance = True

    id = ma.auto_field()
    name = ma.auto_field()
    email = ma.auto_field()
    is_active = ma.auto_field()
    first_name = ma.auto_field()
    last_name = ma.auto_field()
    created_at = fields.DateTime(format='%Y-%m-%dT%H:%M:%S+09:00')
    updated_at = fields.DateTime(format='%Y-%m-%dT%H:%M:%S+09:00')
    full_name = fields.Method("get_full_name")


    Schemaは「1ユーザー＝1行」用。
複数形  guest_list や guests（複数人）は、全体の一覧 → Schemaには入れない
```

引用元: https://techblog.recochoku.jp/3107

###  おすすめギッハブ

https://github.com/wpcodevo/flask-note-taking-api/blob/master/Note%20App.postman_collection.json



###  参考サイト(初心者向け)

https://auth0.com/blog/developing-restful-apis-with-python-and-flask/

https://www.moesif.com/blog/technical/api-development/Building-RESTful-API-with-Flask/

https://medium.com/@dennisivy/flask-restful-crud-api-c13c7d82c6e5

https://pythonbasics.org/flask-rest-api/

https://scrapbox.io/PythonOsaka/FlaskによるWebサービスの実装_Part1
```

### API作り基本(Restful API)

```ruby:
# データ取得 (GET) + .get
users = {
    1: {"name": "John", "birthdate": "1990-01-01"},
    2: {"name": "Jane", "birthdate": "1995-05-05"}
}

@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = users.get(user_id)
    if user:
        return jsonify(user)  # ユーザー情報をJSONで返す
    return jsonify({"message": "User not found"}), 404

# 新規作成 (post)
users = {}

@app.route('/api/user', methods=['POST'])
def add_user():
    user_data = request.get_json()  # JSONデータを取得
    user_id = len(users) + 1  # ユーザーID自動生成
    users[user_id] = user_data
    return jsonify({"message": "User added", "user_id": user_id}), 201

# 更新 (PUT) + .update

users = {1: {"name": "John", "birthdate": "1990-01-01"}}

@app.route('/api/user/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    if user_id in users:
        user_data = request.get_json()  # 更新するデータを取得
        users[user_id].update(user_data)  # ユーザー情報を更新
        return jsonify({"message": "User updated"}), 200
    return jsonify({"message": "User not found"}), 404


# 削除(DELETE) + del [消したいもの]
users = {1: {"name": "John", "birthdate": "1990-01-01"}}

@app.route('/api/user/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    if user_id in users:
        del users[user_id]  # ユーザー削除
        return jsonify({"message": "User deleted"}), 200
    return jsonify({"message": "User not found"}), 404
```

### 複数人をリストに追加　samplecode

```ruby:
from flask import Flask
from flask_restful import Resource, Api, reqparse

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

guest_list = ['Akansh', 'Bill Gates', 'Elon Musk']

parser = reqparse.RequestParser()
parser.add_argument('name', type=str, help='Name of guest')


class GuestList(Resource):
    def get(self):
        return {'guests': guest_list}, 200


class GuestById(Resource):
    def get(self, id):
        if id < 0 or id >= len(guest_list):
            return {'error': 'Guest not found'}, 404
        return {'name': guest_list[id]}, 200

    def put(self, id):
        args = parser.parse_args()
        name = args['name']
        if id < 0 or id >= len(guest_list):
            return {'error': 'Guest not found'}, 404
        guest_list[id] = name
        return {'message': 'Updated', 'guests': guest_list}, 200

    def delete(self, id):
        if id < 0 or id >= len(guest_list):
            return {'error': 'Guest not found'}, 404
        deleted_name = guest_list.pop(id)
        return {'message': f'{deleted_name} deleted', 'guests': guest_list}, 200


class GuestAdd(Resource):

    def post(self):
        args = parser.parse_args()
        name = args['name']
        guest_list.append(name)
        return {'message': 'Added', 'guests': guest_list}, 201


api.add_resource(GuestList, '/guests')
api.add_resource(GuestById, '/guest/<int:id>')
api.add_resource(GuestAdd, '/guest')
```

### Comment → Post → User 　スキーマ

```ruby:schema
from flask_marshmallow import Marshmallow
from marshmallow import fields

ma = Marshmallow()

class UserSchema(ma.SQLAlchemySchema):
    class Meta:
        model = User
        load_instance = True

    id = ma.auto_field()
    username = ma.auto_field()

class PostSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Post
        load_instance = True

    id = ma.auto_field()
    content = ma.auto_field()
    user = ma.Nested(UserSchema)  # ← Post の中に User を入れる！

class CommentSchema(ma.SQLAlchemySchema):
    class Meta:
        model = Comment
        load_instance = True

    id = ma.auto_field()
    text = ma.auto_field()
    post = ma.Nested(PostSchema)  # ← Comment の中に Post（→ さらに User）を入れる！

```

```ruby:model
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

db = SQLAlchemy()
ma = Marshmallow()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', backref='posts')

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    text = db.Column(db.String)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
    post = db.relationship('Post', backref='comments')
```

```ruby:json
{
  "id": 1,
  "text": "Nice post!",
  "post": {
    "id": 10,
    "content": "Flaskは楽しい！",
    "user": {
      "id": 5,
      "username": "YamadaTarou"
    }
  }
}


### json出力例
CommentSchema().dump(comment_obj) 
```
リレーションを含める	ma.Nested(他のSchema)


### 複数データをまとめて表示（リスト表示)

```ruby:全コメント一覧」を取得したい
comments = Comment.query.all()
CommentSchema(many=True).dump(comments)

class UserSchema(ma.SQLAlchemySchema):
    class Meta:
        model = User
        load_instance = True

    id = ma.auto_field()
    username = ma.auto_field()
    posts = ma.Nested(PostSchema, many=True)
```

id = fields.Int(dump_only=True)  # 出力のみ。入力❌ dump?only = true
password = fields.Str(load_only=True)  # 入力だけ。出力には含めない

post = fields.Nested(PostSchema)  # Comment スキーマでPostも表示

TypeError: Object of type X is not JSON serializable	Nested()がない or many=Trueの付け忘れ
AttributeError: 'dict' object has no attribute	load_instance = Trueがないと、dictしか返らない

name = fields.Str(required=True, validate=Length(min=2))


POSTで受け取ったJSONをUserモデルに自動で変換して保存できる : load_instance = True

###IDでゲストを検索し、guest.nameを更新

```ruby:
# PUTリクエストで更新
class GuestUpdate(Resource):
    def put(self):
        # JSONデータを解析
        args = parser.parse_args()
        guest_id = args['id']
        name = args['name']
        
        # IDでゲストを検索して更新
        guest = Guest.query.get(guest_id)
        if guest:
            guest.name = name  # 新しい名前に更新
            db.session.commit()  # 変更をデータベースに保存
            return {'message': 'Guest updated successfully'}
        return {'message': 'Guest not found'}, 404
```

### 削除するレコードが存在しない場合、または編集する際に、入力値が不正な場合などを事前にチェック

```ruby:
from marshmallow import ValidationError

# 編集時のバリデーション
class GuestUpdate(Resource):
    def put(self):
        args = parser.parse_args()
        guest_id = args['id']
        name = args['name']
        
        if len(name) < 3:
            raise ValidationError('Name must be at least 3 characters long')
        
        guest = Guest.query.get(guest_id)
        if guest:
            guest.name = name
            db.session.commit()
            return {'message': 'Guest updated successfully'}
        return {'message': 'Guest not found'}, 404
```

#### 一括削除 argsの中身が複数形に

```ruby:argsの中身が複数形になる
# 複数ゲスト削除（DELETEリクエスト）
class BatchDelete(Resource):
    def delete(self):
        args = parser.parse_args()
        guest_ids = args['ids']  # 複数のIDをリストで受け取る
        
        for guest_id in guest_ids:
            guest = Guest.query.get(guest_id)
            if guest:
                db.session.delete(guest)
        
        db.session.commit()  # 一括で変更を反映
        return {'message': f'{len(guest_ids)} guests deleted successfully'}
```

###エラー処理　dataがない以外のエラーも必ずつける

```ruby:dataなし以外
except Exception as e:
    return {'message': f'Error: {str(e)}'}, 500
```

```ruby:全体
guest = Guest.query.get(guest_id)
            if guest:
                # 処理
                return {'message': 'Guest updated successfully'}
            else:
                return {'message': 'Guest not found'}, 404
        except Exception as e:
            return {'message': f'Error: {str(e)}'}, 500
```

### Outlookメール　＋REstfulAPI

Azure ADでアプリ登録 → OAuth 2.0でアクセストークン取得

https://medium.com/@megan.baye/access-microsoft-outlook-mail-using-python-through-the-microsoft-graph-api-4b8d11a0aae5

https://medium.com/@dennisivy/flask-restful-crud-api-c13c7d82c6e5

```ruby:ajaxでメールデータ取得
$.ajax({
    type:"GET",
    dataType: "json",
    data:{'name':'Payam'},
    url: "http://mypythonapp-spacepirate.rhcloud.com/hello/",
    data: { name: 'Payam' },
    dataType: "json",
    success: function(data) {
        console.log(data);  // => {key: "Hello World!", q: "Payam"}
    },
    error: function(err) {
        console.error("AJAX Error:", err);
    }
})

# Flaskでこう書く
@app.route('/hello/', methods=['GET'])
def hello_world():
    name = request.args.get('name')
    response = {'key': 'Hello World!', 'q': name}
    return jsonify(response)
```

### mysql接続　

```ruby:
from flask import current_app
from flask_sqlalchemy_session import flask_scoped_session
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session

class WriteEngine:
    def __init__(self):
        username = 'hogehoge'
        password = 'mogemoge'
        hostname = 'localhost'
        dbname   = 'rest'
        charset  = 'utf8mb4'
        url = f'mysql+mysqldb://{username}:{password}@{hostname}/{dbname}?charset={charset}'
        self.engine = create_engine(url, echo=True)

class WriteSession(WriteEngine):
    def __init__(self):
        super().__init__()
        self.session = scoped_session(sessionmaker(bind=self.engine))
```



### dairy

```php:
 Dockerより軽 初心者向. :  Podman Desktop


→Heroku : Render  / Fly.io


管理者機能 いる状況
→ 使うユーザー 多  (不正利用防止)


・ Flaskでレスポンシブ
   → HTMLに " <meta name="viewport" content="width=device-width, initial-scale=1.0">"
```

### デザインパターン編　参考サイト

https://realpython.com/ 

https://flask.palletsprojects.com/ 

 https://flask.palletsprojects.com/en/2.0.x/patterns/ 
 
 https://blog.miguelgrinberg.com/ 


```ruby:envファイルから環境変数を読むライブラリ : python-dotenv

[envファイル]
export FLASK_ENV=development
export FLASK_APP=src
export FLASK_RUN_PORT=8000
export FLASK_RUN_HOST=0.0.0.0
SECRET_KEY=super-secret
DATABASE_URL=mysql://user:pass@localhost/db


[python]
from dotenv import load_dotenv
import os

load_dotenv()
secret = os.getenv('SECRET_KEY')
```

### postgresql使う時の、envファイル書き方

```php:
[adapterをインストしてから、envファイルに書く]
pip install psycopg2-binary


[envファイル]
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=flask_db

PGADMIN_DEFAULT_EMAIL=admin@admin.com
PGADMIN_DEFAULT_PASSWORD=password123
```

## ajax 空文字入力禁止(html)

```
'name': {
    'type': 'string',
    'required': True,
    'empty': False
}
```


```ruby:パスワードチェックとメールチェック python

'email': {
'required': True,
'empty': False,
'regex': r'^[\w\.-]+@[\w\.-]+\.\w+$'
'regex': '^(?=.*[a-zA-Z])(?=.*[0-9]).+$'
}
```

```ruby:ヘッダーせキュリテぃ強化
from flask_talisman import Talisman
Talisman(app)


[やり方]
pip install Flask-Talisman
```


```ruby:API呼び出し時のサイトを明記
from flask_cors import CORS
CORS(app, origins=['https://example.com'])


[やり方]
pip install Flask-Cors
```


```ruby: token セキュリティ強化
import jwt

secret = 'secret-key'
token = jwt.encode({'id': 1}, secret, algorithm='HS256')
decoded = jwt.decode(token, secret, algorithms=['HS256'])  # {'id': 1}

[やり方]
pip install PyJWT
```