flaskを使ってRestAPIサーバを作ってみる(削除編)

# 本記事は

[flaskを使ってRestAPIサーバを作ってみる](https://qiita.com/kaorunix/items/c5fb96124dcdf57c151f)の「削除」について記述しています。
ここまでの流れは[flaskを使ってRestAPIサーバを作ってみる](https://qiita.com/kaorunix/items/c5fb96124dcdf57c151f)を参照してください。

削除編で説明するコードは `feature/chapter2-delete` ブランチにあります。
**flask_sv** プロジェクトのディレクトリ配下で次のコマンドを実行してください。

```
$ git checkout feature/chapter2-delete
```

# 5. 削除

削除のAPIは、アカウントIDだけわかれば良いのでGETメソッドで特定のURLにアクセスすることにしましょう。

`http://サーバーのIP/account/delete/アカウントID`

### 5.1. モデル層・エンティティ

コードの全文はこちらです。


```backend/src/model/Account.py
def delete(account_id, operation_account_id):
    Session = scoped_session(sessionmaker(bind=engine, autocommit=False))
    ses = Session()
    account_record = ses.query(Account).with_for_update().get(account_id)
    try:
        account_record.status=Status.getStatusKey("DELETE")
        ses.add(account_record)
        ses.commit()
        message = ""
        res = True
    except Exception as e:
        message = str(e)
        print(f"Account#update error:{message}")
        ses.rollback()
        res = False
    finally:
        ses.close()
    return (res, message)    
```

コードの解説をします。

[4.4. 排他編集]()で説明したscoped_sessionを使っています。
排他編集同様にaccount_idからのロックを行います。

```Account.py
def delete(account_id, operation_account_id):
    Session = scoped_session(sessionmaker(bind=engine, autocommit=False))
　　　　　　　　ses = Session()
    account_record = ses.query(Account).with_for_update().get(account_id)
```

次に削除ですが実際にレコードを削除しても良いですが、本システムでのレコードは論理削除を行います。

```Account.py
    try:
        account_record.status=Status.getStatusKey("DELETE")
        ses.add(account_record)
        ses.commit()
        message = ""
        res = True
    except Exception as e:
        message = str(e)
        print(f"Account#update error:{message}")
        ses.rollback()
        res = False
    finally:
        ses.close()
```

論理削除をする理由としてはオペレーションミスによる削除から復旧したり、過去のオペレーション履歴を調査することです。また、論理削除ではデータが溜まっていくため、一定期間たったら削除するためのバッチを動かしたり、保守用のテーブルに移動するなどしておかないとDB全体でのパフォーマンス低下、ディスク圧迫を引き起こすので覚えておいてください。

返却は処理結果とエラー時のメッセージのタプルとしています。

```Account.py
    return (res, message)    
```

もし、物理削除する場合は次の様に検索結果にdelete()関数を付加します。

```python
ses.query(Account).get(account_id).delete()
```

### 5.2. ドメイン層・ビジネスロジック

コードの全文はこちらです。


```backend/src/restapi/AccountApi.py
def delete(account_id, operation_account_id):
    """
    /account/deleteで呼び出されたAPIの検索処理

    Parameters
    ----------
    account_id : int
        削除するアカウントID
    operation_account_id : int
        Webアプリケーション操作アカウントのID

    Returns
    -------
    JSON形式の処理結果
        正常
        異常
    """

    print(f"AccountApi#delete account_id={account_id}")
    message=""
    code=""
    try:
        res = Account.delete(account_id, operation_account_id)
        if res[0] == True:
            code="I0001"
            message="deleted Account Succesfuly."
        else:
            code="E0001"
            message=res[1]
        
    except Exception as e:
        code="E0009"
        message=f"Deleted failed:{str(e)}"
    
    result_json = {
        "body": "",
        "status": {
            "code" : code,
            "message" : message,
            "detail" : ""
        }
    }
    ret = result_json
    return ret
```

コードの解説をします。

Account.deleteで処理した結果をアプリケーション層へ返却します。

```AccountApi.py
    try:
        res = Account.delete(account_id, operation_account_id)
        if res[0] == True:
            code="I0001"
            message="deleted Account Succesfuly."
        else:
            code="E0001"
            message=res[1]
        
    except Exception as e:
        code="E0009"
        message=f"Deleted failed:{str(e)}"
```

処理結果をJSONに詰め直し返却します。

```AccountApi.py    
    result_json = {
        "body": "",
        "status": {
            "code" : code,
            "message" : message,
            "detail" : ""
        }
    }
    return result_json
```

### 5.3. アプリケーション層・フロントへのIF

`http://サーバーのIP/account/delete/100` にGETでアクセスされたらAccountApi.deleteを実行して結果をJSON形式で返却します。

```backend/src/api.py
@api_bp.route('/account/delete/<id>', methods=['GET'])
def deleteAccount(id):
    account_json = AccountApi.delete(id, system_account_id)
    return jsonify(account_json)
```

##### 課題 5.1

5.2. ドメイン層・ビジネスロジックのコードではロックをしてから削除までユーザの手が入れられません。
ユーザーがロックをしたら安心して次のオペレーション(削除)を行え、かつ、他のユーザーにロックされていたら次の処理を待てる様にロックの時に
`http://サーバーのIP/account/lock/100`
を呼び出し、次に
`http://サーバーのIP/account/delete_for_lock/100`
を呼び出すことで削除が行われる様に変更してください。
また、ロックをしたセッションを保存するためのコードの作成も必要となります。

### 5.4. テストコードからの実行

テストコード全文

```backend/tests/restapi/test_AccountApi.py
def test_account_delete():
    """
    """
    account = {
        'account_name' : "delete_account",
        'start_on' : '2021-05-23 00:00:00',
        'end_on' : '2030-12-31 00:00:00',
        'created_by' : 999,
        'created_at' : datetime.datetime.now(),
        'updated_by' : 999,
        'updated_at' : datetime.datetime.now(),
        'status' :  Status.getStatusKey("NEW")
    }

    # create
    Account.create(account, 999) == True

    search_query = {
        "account_name":"delete_account",
        "start_on":"2021-05-23 00:00:00",
        "end_on":"2030-12-31 00:00:00"
    }
    result = Account.search(search_query, 999)
    assert result[0].account_name == account['account_name']
    account_id = result[0].id

    # APIから確認
    url = f"http://localhost:5000/api/account/delete/{account_id}"
    headers = {'Accept-Encoding': 'identity, deflate, compress, gzip',
               'Accept': '*/*', 'User-Agent': 'flask_sv/0.0.1',
               'Content-type': 'application/json; charset=utf-8',
               }
    response = requests.get(url, headers=headers)

    # HTTP Statusコードが200であること
    assert response.status_code == 200

    data = json.loads(response.text)
    print(f"test_AccountApi#test_account_delete data={data}")
    assert data['body'] == ""
    assert data['status']['code'] == "I0001"
    assert data['status']['message'] == "deleted Account Succesfuly."
```

コードの解説をします。

テストAccountを作ります。その元になるパラメータです。

```test_AccountApi.py
    account = {
        'account_name' : "delete_account",
        'start_on' : '2021-05-23 00:00:00',
        'end_on' : '2030-12-31 00:00:00',
        'created_by' : 999,
        'created_at' : datetime.datetime.now(),
        'updated_by' : 999,
        'updated_at' : datetime.datetime.now(),
        'status' :  Status.getStatusKey("NEW")
    }
```

上記のパラメータをAccountのcreateで作成します。

```test_AccountApi.py

    # create
    Account.create(account, 999) == True
```

次に検索をして作成されたAccountのIDを調べます。

```test_AccountApi.py

    search_query = {
        "account_name":"delete_account",
        "start_on":"2021-05-23 00:00:00",
        "end_on":"2030-12-31 00:00:00"
    }
    result = Account.search(search_query, 999)
    assert result[0].account_name == account['account_name']
    account_id = result[0].id
```

テスト対象のAPIを実行します。

```test_AccountApi.py

    # APIから確認
    url = f"http://localhost:5000/api/account/delete/{account_id}"
    headers = {'Accept-Encoding': 'identity, deflate, compress, gzip',
               'Accept': '*/*', 'User-Agent': 'flask_sv/0.0.1',
               'Content-type': 'application/json; charset=utf-8',
               }
    response = requests.get(url, headers=headers)
```

返却されたレスポンスの結果を確認します。

```test_AccountApi.py
    # HTTP Statusコードが200であること
    assert response.status_code == 200

    data = json.loads(response.text)
    assert data['body'] == ""
    assert data['status']['code'] == "I0001"
    assert data['status']['message'] == "deleted Account Succesfuly."
```

このテストコードではエラー時のテストがされていません。
実際は、全てのパターンを網羅してテストコードを準備するのが望ましいです。

##### 課題 5.2

本テストコードはエラー時のテストができないので、削除しようとしたアカウントIDが存在しない場合のテストコードを作成してください。

##### 課題 5.3

削除しようとしたアカウントIDが既に削除されている場合のテストコードを作成してください。

----

#### 1. 参照 [flaskを使ってRestAPIサーバを作ってみる(参照編)](https://qiita.com/kaorunix/items/05f47b937d83e2324c4d)

#### 2. 作成 [flaskを使ってRestAPIサーバを作ってみる(作成編)](https://qiita.com/kaorunix/items/00eae1270265629d7033)

#### 3. 検索 [flaskを使ってRestAPIサーバを作ってみる(検索編)](https://qiita.com/kaorunix/items/d510d70e47e4ee27cdc3)

#### 4. 編集 [flaskを使ってRestAPIサーバを作ってみる(編集編)](https://qiita.com/kaorunix/items/51e1d223d8c6a8d28bf6)

#### 5. 削除 [flaskを使ってRestAPIサーバを作ってみる(削除編)](https://qiita.com/kaorunix/items/2ac5605a3cf5b3b9439a)