まず結論から書くと、下記の図のような事ができます。
上側に表示しているTypeScriptで書いたプログラムを、下側のPythonプログラムから実行している例です。
これは、jsiiというライブラリを使って、TypeScriptプログラムをPythonで実行可能なライブラリに変換しています。公式ドキュメントにthe magic happens
と書いてあるとおり、魔法のような技術です。
jsiiはPythonの他にもJavaなどの言語で利用できますが、本稿ではTypeScriptで書いたプログラムをPythonで実行します。
実際にやってみましょう。
本稿の実行環境
- TypeScript 3.6.4
- Python 3.7.5
- Node.js 13.1.0
jsiiを使ってみる
jsiiは大まかに、下記のような流れで利用できます。
- TypeScriptでプログラムを記述
- jsiiを使って他言語のパッケージをビルド
- 他言語からインストールして利用する
TypeScriptプログラムの記述
サンプルとして、TypeScriptで適当なFizzBuzzのプログラムをlib/index.ts
に記述します。
export class MyClass {
fizzBuzz(arg: number): string {
if (arg % (3 * 5) === 0) {
return 'FizzBuzz';
} else if (arg % 3 === 0) {
return 'Fizz';
} else if (arg % 5 === 0) {
return 'Buzz';
}
return arg.toString();
}
}
もしTypeScriptとして普通に実行することで動作確認したい場合は、$ npx ts-node lib/index.ts
などお好みの方法で実行します。
package.jsonの記述
jsiiはビルド時にpackage.json
の情報を使って、各言語のライブラリ用のメタ情報を登録していきます。そのため、ある程度package.json
を「ちゃんと」書いていないとjsiiを動作させることはできません。
下記が設定例です。
{
"name": "jsii-example",
"version": "1.0.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"author": "your_name",
"repository": {
"url": "https://example.com",
"type": "Git"
},
"license": "MIT",
"jsii": {
"outdir": "dist",
"targets": {
"python": {
"distName": "your_name.jsii_example",
"module": "your_name.jsii_example"
}
}
},
"devDependencies": {
"jsii": "^0.20.6",
"jsii-pacmak": "^0.20.6"
}
}
上記のようにrepository
やauthor
など普段省略しがちな箇所が、jsii利用時には必須になります。またmain
とtypes
には、lib/index.ts
と同じディレクトリで指定する必要があります。
加えてjsii固有の設定として、jsii
という所に各言語のパッケージ用の情報を記述します。今回はPythonのパッケージをビルドするため、targets
にpython
としてモジュール名の情報を記述しています。
jsiiでのビルド
jsii
のビルド実行に必要なパッケージをインストールします。
$ npm install --save-dev jsii jsii-pacmak
$ echo "jsii
wheel
publication" > requirements.txt
$ pip install --require requirements.txt
上記インストールが完了したらjsii
コマンドでビルドし、jsii-pacmak
コマンドでパッケージングします。
$ npx jsii
$ npx jsii-pacmak
dist/python
ディレクトリに、your_name.jsii_example-1.0.0.tar.gz
が作成されていたら成功です。
Pythonから実行する
先程作成したパッケージファイルを、Pythonライブラリとしてインストールします。
$ pip install dist/python/your_name.jsii_example-1.0.0.tar.gz
このライブラリをimportして、先程のfizzBuzz関数を呼び出すPythonプログラムを記述します。
import your_name.jsii_example
obj = your_name.jsii_example.MyClass()
print(obj.fizz_buzz(2))
print(obj.fizz_buzz(3))
print(obj.fizz_buzz(5))
print(obj.fizz_buzz(15))
いよいよ実行します。
$ python python.py
2
Fizz
Buzz
FizzBuzz
TypeScriptで書いたコードを、Pythonから呼び出すことができました!
Dockerでjsiiを使う
ここまで述べてきた実行環境構築は複数言語のインストールが必要だったりと少々面倒のため、今回jsii
実行に必要なPythonとNode.jsをまとめて用意できるDockerfileを用意しました。
FROM python:3.7.5-slim
ENV DIR /app
WORKDIR $DIR
RUN apt update && apt install -y nodejs npm
# setup python
COPY requirements.txt $DIR
RUN pip install --requirement requirements.txt
# setup node
COPY package.json $DIR
RUN npm install
# main: lib/index.ts
COPY lib/ $DIR/lib
先述のpackage.json
,requirements.txt
,lib/index.ts
を用意した後、下記のようにして使います。
$ mkdir dist
$ docker build --tag jsii-example .
$ docker run --rm --volume $(pwd)/dist:/app/dist jsii-example /bin/bash -c 'npx jsii && npx jsii-pacmak'
dist/
以下にパッケージファイルが生成されたら成功です。
jsiiの仕組み
jsiiの仕組みについては、jsiiのドキュメント内に解説があります。
jsii/runtime-architecture.md at master · aws/jsii
Python等のホストコードがjsii利用箇所に到達した時、nodeの子プロセスを立ち上げてプログラムを実行し、その結果をPythonで受け取っているようです。ホストの/tmp
ディレクトリを見てみると、プログラムを一時的にnpm installしていると思しき形跡が見つかると思います。
本来Pythonだけで終わる処理がNode.jsとの通信にも依存するようになっているため、パフォーマンスは犠牲になっている点がドキュメント内でも述べられています。jsiiは本番環境などパフォーマンスが要求されるアプリケーション内での動作は難しく、開発やビルド用ツール等での利用が想定されています。
jsiiの利用例
jsiiはAWSが開発しているOSSです。実際の使用例として、AWSの提供するInfrastructure as CodeのツールであるAWS CDK内でjsiiが使われています。aws-cdkはjsiiと同じく、TypeScript、Python、.NET、Javaでインフラ構成を記述できるようになっています。
AWS クラウド開発のよくある質問 より引用
AWS は、AWS Construct ライブラリパッケージのビジネスロジックを TypeScript で構築し、サポートされている各プログラミング言語へのマッピングを提供します。これにより、AWS CDK コンストラクトの動作が異なる言語間で一貫していることを確認でき、すべての言語で利用できる包括的なコンストラクトパッケージのセットを提供できます。AWS CDK プロジェクトで作成したコードはすべてお客様ご希望のプログラミング言語でネイティブになっています。JavaScript ランタイムはお客様のプログラミング経験の実施詳細です。jsii プロジェクトは https://github.com/aws/jsii で参照できます。
Tips
- jsiiで実行できるTypeScriptには制限がある
- 分かりやすい例を挙げると、他言語の予約語をTypeScriptで使用しようとすると正常に動作しない場合があります。
- 他、どういった制限があるかはドキュメントに記載されています。
- Python 3.8には未対応
- 本稿ではPython 3.7.5で実行しています。3.8.0を使うと、本稿執筆中の時点ではエラーが発生してしまいjsiiで作られたライブラリを実行することができません
- issueも立っています
- Ruby対応は開発中
- ドキュメントにハッキリとは書いていないのですが、Historyやソースコード内を読んでいくとRubyに対応しているかのような記述が所々に見つかります。
- package.json内の
targets
にruby
と指定すると、ちゃんとjsii
は動作してdist/ruby/
にパッケージらしきファイルが生成されるのですが、TypeScriptのソースコードをそのまま含めるプログラムが動いているだけのようでgemの生成は実装されていません。 - Ruby対応についてはissueが立っています
まとめ
jsiiを使って、TypeScriptで書いたプログラムをPythonのソースコード内で実行することができました。まだ開発中の部分もあり制約は多いものの、実際にAWS CDKで使われているため一定の将来性もあります。もし「1つのコードベースで作ったツールを、複数の言語から利用したい」という場合には、このjsiiが選択肢に挙がるのではないかと思います。
今回利用したプログラムはGitHubにも上げています。もし興味を持った方はご覧ください。
https://github.com/s2terminal/jsii-example