カンナートのエンジニアチームに所属しているOAと申します。
この記事では、とある案件にてPythonソースをLaravelに組み込む必要がでたとき得た知見を紹介いたします。
目次:
・要約
・PythonをLaravelで利用したい場合、どう実装するか?
・なぜProcessコンポーネントを勧めるのか?
・ソースコード解説
・サーバーデプロイ後のトラブルシューティングに役立ちそうなリスト(Python + Laravelに限る。)
要約:
主張:LaravelからPythonを利用する場合、Symfonyに付属しているProcessコンポーネントを利用するのがおすすめです。ProcessFailedExceptionが非常に優秀です。
インストール:
1 |
composer require symfony/process |
自分が利用したソース例:
1 2 3 4 5 6 7 8 9 10 11 12 |
use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; $process = new Process(['command1', 'command2', 'command3', ...]); try { $process->mustRun(); echo $process->getOutput(); } catch (ProcessFailedException $exception) { echo $exception->getMessage(); } |
注意点:
pythonファイルを実行する場合、例えばコマンドラインからは
1 |
python3 example.py |
としている場合、例えば
1 |
/hogehoge/python3 /dummy/example/Python/example.py |
とフルパスで指定する。
PythonをLaravelで利用したい場合、どう実装するか?:
自分なりに色々調べた結果、
「Python実行ファイルをLaravel内部に仕込んでおき、PHP内部でPython実行コマンドを呼び出す」結論に至りました。
Pythonで実行した結果を返すAPI専用プロジェクトを作成し、別サーバーにおく案もありましたが、
納期的に数日レベルだったのと、お客様があまりコストをかけたくないのとで、工数が少なそうなPython組み込み方式を選択した次第です。
なぜProcessコンポーネントを勧めるのか?:
結論からいうと、「PHPの組み込み関数群よりエラーハンドリングが優秀だから」です。
PHPにはコマンドをプログラム実行時に実行できるexecやsystemといった関数が標準で存在しますが、
エラーがでたとき値を返さないときがある、try~catchで拾えないときがある、という不便さを持ちます。
execを例にProcessと比較してみます。
シナリオ:
①pythonソースで外部モジュールをimportし利用している。各環境ではpip3 install hogehogeなどで持ってくる必要がある。
十分デバッグしている。
1 2 3 4 5 |
from hogehoge import hoge function getHoge(){ print(hoge.sayHoge()) } |
単体の実行結果:
1 2 |
> python3 example.py hoge! |
Laravel上での処理:
1 2 3 4 5 6 7 8 9 |
namespace App\Http\Controllers; class ExecController extends Controller { $pycommand = "path/to/python3 path/to/example.py"; exec($pycommand, $reuslt); \Log::info($result); // もしくは $result = exec($pycommand); としてもreusltは出力可能 } |
実行後ログ:
1 |
[2021-09-08 20:26:23] local.INFO:hoge |
②ローカル環境で動くことが確認できたのでサーバー(AWS EC2など)にデプロイした。
③事象:execを実行してもなにも返ってこない。
「なにも返ってこない」ので切り分け作業の量と時間がともに増えます。
エラーメッセージは役立たないときもありますが、問題の切り分けをするとき役立つことが多いです。
エラーメッセージが欲しいのでLaravelソースを下記のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
namespace App\Http\Controllers; // 追加-------------- use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; // --------------------- class ExecController extends Controller { // 先のpythonコマンドを下記のようにあてはめます。 $process = new Process(['path/to/python3', 'exmaple.py']); try { $process->mustRun(); \Log::info($process->getOutput()); } catch (ProcessFailedException $exception) { \Log::error($exception->getMessage()); } } |
実行後、ログにはたとえば(下記は正確ではありません)
1 2 3 |
Traceback (most recent call last): No module not found error: no module hogehoge ・・・・・ |
とpython側のエラーが表示されます。
以上から、原因として「サーバーデプロイ後、hogehogeライブラリをインストールしてないことが原因だった」とわかります。
このように、「呼び出したソースで発生したエラーを吐き出してくれる」という強力な機能を持ちます。
補足:「十分にデバッグしている」前提があると書いてあるくせになぜPythonソース側のエラーを吐き出してくれるものを勧めるのか?という疑問はごもっともです。デバッグでは自分の認識外はカバーできません。この認識外を少しでも出すのに役立つので紹介しております。
ソースコードの解説:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
namespace App\Http\Controllers; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; class ExecController extends Controller { $process = new Process(['path/to/python3', 'exmaple.py']); try { $process->mustRun(); \Log::info($process->getOutput()); } catch (ProcessFailedException $exception) { \Log::error($exception->getMessage()); } } |
$process = new Process([]) :
Processの中にはarrayでコマンドを渡します。
ls -la であれば [‘ls’, ‘-la’]
python3 process.pyであれば[‘python3’, ‘process.py’]
など。
もしpythonが動かない場合、フルパスを渡すと良いです。
また、arrayに分割してパックするのが面倒な場合、下記の書き方でも代用可能です。
ちゃんとCLIなどで実行して動くと確認したものを入れると良いです。
1 2 |
$command = 'ls -la'; $process = Process::fromShellCommandline($command); |
$process->mustRun():
公式だと
The
mustRun()
method is identical torun()
, except that it will throw aSymfony\Component\Process\Exception\ProcessFailedException
if the process couldn’t be executed successfully (i.e. the process exited with a non-zero code):
とあるように、ProcessFailedExceptionを投げるために必要です。
本記事の肝とも言えます。
$process->getOutput():
実行結果を取得できます。
pythonでいえばprint(‘hogehoge’)と書いた個所をCLIなどで表示される個所を取得できます。
1 2 |
> python3 example.py hogehoge |
上記でいうhogehogeです。
$process->getMessage():
例外がキャッチされ、どんなエラーが出たのか見たいとき利用します。
Laravelであれば
1 |
\Log::error($process->getMessage()); |
とでも書いてstorage/logs/laravel.log(デフォのパス)などに出力すると良いかと思います。
本記事の第二の肝です。
サーバーデプロイ後チェックリスト(Python + Laravelに限る。):
python3系(or2系)をインストールしているか
pipなどで必要なライブラリをインストールしているか
python3ならばpip3 install hogehogeと書いてインストールしたか
python自体へのパスを取得したか。
1 2 3 |
import os import sys os.path.dirname(sys.executable) |
python実行ファイルへのフルパスを取得したか。
(手っ取り早いのはサーバー上で該当ファイルの存在するディレクトリでpwdコマンドの結果をコピペ)
以上となります。
ちなみに「pip周りはshell scriptで持っておいて一緒に環境に持って行けよ・・・」というツッコミはごもっともです。
良い開発ライフをお送りください。