serverless framework の記事ばかり投稿している遠藤です。

serverless はとても便利なのですが、個人的に惜しい点が1つあり、それが PyCharm で step 実行できない(できるのかもしれないけど、正しいやり方を知らない)点です。

今回は、応急処置的に、 break point を張って step 実行する方法を備忘録として記載いたします。
ちょっとした小ネタ記事になりますのでご了承ください。

環境

  • OS: macOS Monterey
  • python: 3.6.9
  • PyCharm: 2021.1.2(Professional Eddition)
  • serverless:
    • Framework Core: 2.71.0
    • Plugin: 5.5.3
    • SDK: 4.3.0
    • Components: 3.18.1

コードをいじる

まず、 step 実行したい function の handler に少し手を加えます。

serverless.yml に以下のような定義をしていた場合

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

実行部分は以下のようになっているかと思います。

import json


def hello(event, context):
    response = {"statusCode": 200, "body": "body"}
    return response

この先頭部分に、 sleep を入れます。
個人的には 20 秒くらいがおすすめです。

import json
import time  # ← 追加


def hello(event, context):
    time.sleep(20)  # ← 追加
    response = {"statusCode": 200, "body": "body"}
    return response

BreakPoint を張る

次に、 sleep の後ろくらいで、 適当に break point を張っておきます。
以下のように、赤枠を囲ったあたりをクリックすると、赤い丸印が付き、処理が入ってきた時に、一時停止できます。

----------2022-01-12-23.29.59

serverless を実行する

続いて serverless を local 実行します。
実行方法は、

$ serverless invoke local -f {function_name}

でも

$ serverless offline start # (要プラグイン)

でも構いません。
API ではない場合などもあるので、 invoke の方が確実です。

offline start を実行した場合は、起動後に curl などで get なり post などを叩きましょう。

Process に attach する

実行開始したら、 sleep で指定した時間(今回は 20 秒)以内に、 python の process へ PyCharm を attach させます。

PyCharm には実行中の python process に attach する機能がありますが、 serverless コマンドは node.js で実行されるため、デバッグ実行や attach ができません。

ただし、 node.js の内部では最終的に python を呼び出しているので、そのタイミングで sleep を入れてデバッグしちゃおう!という、せせこましい手段になります。

(参考)最終的に python を呼び出している箇所

以下が invoke local した後に実行されている処理ですが、 node.js の child_process を使って invoke.py を呼び出しているのが分かるかと思います。

https://github.com/serverless/serverless/blob/master/lib/plugins/aws/invokeLocal/index.js#L602

    const wrapperPath = await this.resolveRuntimeWrapperPath('invoke.py');

    return new Promise((resolve, reject) => {
      const python = spawn(
        runtime.split('.')[0],
        ['-u', wrapperPath, handlerPath, handlerName],
        { env: process.env },
        { shell: true }
      );
      python.stdout.on('data', (buf) => {
        legacy.consoleLog(buf.toString());
        writeText(buf.toString());
      });
      python.stderr.on('data', (buf) => {
        legacy.consoleLog(buf.toString());
        writeText(buf.toString());
      });
      python.on('close', () => resolve());
      let isRejected = false;
      python.on('error', (error) => {
        isRejected = true;
        reject(error);
      });

      process.nextTick(() => {
        if (isRejected) return; // Runtime not available
        python.stdin.write(input);
        python.stdin.end();
      });
    });
  }

attach の方法

PyCharm のメニューバーから Run を選び

----------2022-01-12-23.28.13

出てきたメニューの中から、 Attach to Process... を選びます
----------2022-01-12-23.28.24

serverless の実行が python まで到達していないと、 attach 可能なプロセスがないと表示されます。

----------2022-01-12-23.28.02

python が起動し、 sleep に入っていると以下のように attach 可能なプロセスが出てきます。
----------2022-01-12-23.58.13

serverless invoke してから、実際に python が走り始めるまで数秒あるため、 sleep の前に、 "start" とかをログ出力しておくと、 attach を仕掛けるタイミングが分かりやすいです。

Let's Debug!

ここまで来たらあとは自由にデバッグできます。
step over するもよし、 step into するもよし、やりたい放題です。
----------2022-01-13-0.06.14

終わりに

sleep を毎回仕込んだり、リリース時に消すのを忘れないように、など面倒な点もありますので、どうしてもステップ実行したい!という緊急用になるかなと思います。

知ってしまえば大したことのない方法ですが、今まで頑張って log を各行に仕込んで動作確認していたので、ほんの少し効率的に開発ができるようになるかもしれません。

ところで、PyCharm でのデバッグ方法を探していたところ、なにやら chalice という面白そうな framework を見つけました。
(なんとこちらは、デフォルトでデバッグできる!)

chalice についても近々、ご紹介できたらなと思っています。