注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
外部トランスフォームを使用すると、Python トランスフォームのリポジトリから外部システムに接続できます。
Data Connection ソースは、コードリポジトリに直接インポートできるようになりました。外部システムとやり取りする際には、資格情報ベースのレガシー外部トランスフォームに代わり、この方法を推奨します。
外部トランスフォームは、認証にアウトバウンドアプリケーションを使用するソースをサポートしていません。
外部トランスフォームは、主に次のいずれかに該当する場合に、バッチ同期、エクスポート、メディア同期のワークフローを実行するために使用されます。
これらの状況への解決策として、次の方法があります。
仮想テーブルを使用するトランスフォームも、外部トランスフォームと見なされます。これは、仮想化されたデータを格納する外部システムにトランスフォームジョブが接続できる必要があるためです。Python トランスフォームで仮想テーブルを使用するには、以下の手順に沿ってソースを設定してください。
このセットアップガイドでは、無料の公開辞書 API ↗ に接続する Python トランスフォームリポジトリを作成する手順を説明します。その後、この API を使った例を通じて、外部トランスフォームのさまざまな機能と、API と組み合わせた使用方法を説明します。
このセットアップガイドで使用する辞書 API は Palantir とは無関係であり、いつでも変更される可能性があります。このチュートリアルは、本番環境のユースケースでこの API を使用することを支持、推奨、または提案するものではありません。
このガイドに沿って進める前に、まず Python トランスフォームリポジトリを作成し、チュートリアルで説明されている Python トランスフォームの作成方法を確認してください。Python トランスフォームのすべての機能は、外部トランスフォームでも利用できます。
Python リポジトリから外部システムに接続するには、コードにインポートできる Data Connection ソースを作成する必要があります。このチュートリアルでは、上記の辞書 API に接続する REST API ソースを作成します。
外部トランスフォームで使用するソースを最も手早く作成するには、Python トランスフォームのコードリポジトリから作成します。リポジトリを初期化したら、次の手順で汎用ソースを設定します。
左側のパネルから、外部システムタブを開きます。
VS Code ワークスペース内の Python トランスフォームリポジトリで作業している場合、外部システムの設定は、上部ナビゲーションバーの設定タブにあります。
追加 > 新規作成を選択します。


このチュートリアルでは、辞書 API の api.dictionaryapi.dev に対するエグレスポリシーを追加してください。この API は認証を必要としないため、シークレットは不要です。また、エクスポート制御は現時点では省略できます。ただし、このソースで Foundry のデータ入力を使用するには、エクスポート制御が必要になります。
この接続先は REST API のため、組み込みの Python requests クライアントを使用できるように、汎用コネクターを REST API ソースに変換するためのダイアログが自動的に表示されます。
Data Connection アプリケーションからソースを作成することも、すでに設定済みのソースを使用することもできます。この方法を使用するには、次の手順に沿って進めます。

概要ページを確認し、右下の続行を選択します。接続ワーカーの選択画面が表示されます。Agent ワーカー接続は外部トランスフォームでは利用できないため、辞書 API に接続するには Foundry ワーカーを選択します。
ソースの名前を選び、保存先のプロジェクトを選択します。
ドメインセクションに API ソースの接続情報を入力します。辞書 API の例の設定を以下に示します。


ソースを作成しただけでは、リポジトリで利用できるようにはなりません。外部システムタブからこのソースを追加するには、前提条件:ソースをコードにインポートするで説明されているように、コードへのインポートを許可する必要があります。
transforms-external-systems ライブラリをインポートする外部トランスフォームを使用するには、まずリポジトリに transforms-external-systems ライブラリをインポートする必要があります。ライブラリをインストールするには、左側のパネルのライブラリタブで目的のライブラリを検索し、インストールを選択します。

複数のドメインを持つ REST API ソースはインポートできません。同じ外部トランスフォームで複数のドメインが必要な場合は、代わりにドメインごとに個別の REST API ソースを作成してください。
まず、REST API ソースをコードにインポートできるようにする必要があります。この設定を行うには、Data Connection でソースに移動し、接続設定 > コードインポート設定タブに移動します。
このソースを Code Repositories にインポートできるようにするオプションをオンにします。このソースをインポートしているコードリポジトリは、このページに表示されます。


ソースをリポジトリにインポートするには、ソースに対して少なくとも編集者のアクセス権限が必要です。権限について詳しくはこちら
Dictionary API ソースをインポートする Python トランスフォームのリポジトリを設定すると、そのソースを使用して外部に接続する Python トランスフォームのコードを記述できるようになります。このページの後半にあるエンドツーエンドの例では、一般的なシステムを対象とした、設定済みの読み取りおよび書き込みワークフローを紹介します。
@external_systems デコレーターをインポートして設定する外部トランスフォームを使用するには、transforms.external.systems ライブラリから external_systems デコレーターと Source オブジェクトをインポートする必要があります。
Copied!1from transforms.external.systems import external_systems, Source
次に、external_systems デコレーターを使用して、トランスフォームに含めるソースを指定します。
Copied!1 2 3@external_systems( dictionary_api_source=Source("ri.magritte..source.e301d738-b532-431a-8bda-fa211228bba6") )
ソースは Data Connection で開くためのリンクとして自動的に表示され、リソース識別子の代わりにソース名が表示されます。
ソースをトランスフォームにインポートすると、get_https_connection() メソッドで組み込みの接続オブジェクトを使用して、ソースの属性にアクセスできます。次の例では、前のステップで設定した Dictionary API ソースのベース URL を取得する方法を示します。
Copied!1dictionary_api_url = dictionary_api_source.get_https_connection().url
ソースに保存されている追加のシークレットや資格情報にも、ソースからアクセスできます。アクセス可能なシークレット名を確認するには、トランスフォームの左側のパネルに移動します。

コード内でシークレットにアクセスするには、次の構文を使用します。
Copied!1dictionary_api_source.get_secret("additionalSecretFoo")
現在、ソースが HTTPS クライアントを提供していない限り、資格情報以外のソース属性にはアクセスできません。たとえば、PostgreSQL ソースでは、hostname や、シークレットではないその他の属性にアクセスできません。
RESTful API を提供するソースでは、ソースオブジェクトを通じて組み込みの HTTPS クライアントを使用できます。このクライアントには、サーバー証明書やクライアント証明書を含め、ソースで指定したすべての詳細が事前に設定されるため、そのまま外部システムへのリクエストを開始できます。
Copied!1 2 3 4 5 6dictionary_api_url = dictionary_api_source.get_https_connection().url dictionary_api_client = dictionary_api_source.get_https_connection().get_client() # dictionary_api_client は、Python の `requests` ライブラリの設定済み Session オブジェクトです。 # GET リクエストの例: response = dictionary_api_client.get(dictionary_api_url + "/api/v2/entries/en/" + word, timeout=10)
または、独自のクライアントやソース固有の Python ライブラリを使用し、ソースオブジェクトから属性と資格情報を取得することもできます。ただし、Agent プロキシランタイム(サンセット)を使用してオンプレミスのシステムに接続する場合は、組み込みのクライアントを使用する_必要があります_。このクライアントには、必要な Agent プロキシの設定が自動的に適用されるためです。
トランスフォームや UDF 内で作業ディレクトリを変更すると(たとえば、os.chdir() を使用すると)、セキュアな接続の確立に必要な環境変数への参照が機能しなくなる可能性があります。
次の例は、単語のリストを順に処理し、Dictionary API から各単語の発音の文字起こしを取得する、完全なトランスフォームを示しています。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34from pandas import DataFrame from transforms.api import transform from transforms.api import Output, Input, TransformContext, transform_pandas from transforms.external.systems import external_systems, Source import pandas as pd import logging logger = logging.getLogger(__name__) @external_systems( dictionary_api_source=Source( "<source_rid>" ) ) @transform_pandas(Output("<output_dataset_rid>")) def compute(dictionary_api_source) -> DataFrame: dictionary_api_url = dictionary_api_source.get_https_connection().url dictionary_api_client = dictionary_api_source.get_https_connection().get_client() words = ["apple", "dog", "cat"] phonetics = [] for word in words: logger.info("Fetching word from api.dictionaryapi.dev : " + word) response = dictionary_api_client.get( dictionary_api_url + "/api/v2/entries/en/" + word ).json() phonetics += [{"word": word, "phonetic": response[0]["phonetic"]}] return pd.DataFrame(phonetics)
外部トランスフォームでは、Foundry の入力データを使用する必要があることがよくあります。たとえば、テーブル形式のデータセットの各行について、追加のメタデータを収集するために API にクエリを実行したい場合があります。また、Foundry のデータを外部のソフトウェアシステムにエクスポートする必要があるワークフローも考えられます。
このようなケースは、エクスポート制御の対象となるワークフローと見なされます。これは、保護された Foundry のデータが、セキュリティ上の保証が不明で、データ来歴が途切れる別のシステムにエクスポートされる可能性があるためです。ソース接続を設定する際、ソースの所有者は、Foundry からデータをエクスポートしてよいかどうかを指定し、エクスポート可能なセキュリティマーキングと組織を指定する必要があります。Foundry は、開発者がセキュリティ上の意図を明確に設定に反映し、情報セキュリティ責任者が外部システムとやり取りするワークフローの範囲と意図を監査できるように、ガバナンス制御を提供しています。
エクスポートは、セキュリティマーキングを使用して制御されます。ソースを設定する際、エクスポート設定で、どのセキュリティマーキングと組織のデータを外部システムに安全にエクスポートできるかを指定します。そのためには、Data Connection アプリケーションでソースに移動し、接続設定 > エクスポート設定タブに移動します。次に、このソースへのエクスポートを有効化オプションをオンにして、エクスポートする可能性のあるデータのマーキングと組織を選択します。
この操作には、該当するデータと組織のマーキングを削除する権限が必要です。エクスポートは、Foundry 内のデータからマーキングを削除することと同等と見なされるためです。
次の操作を許可するには、このソースへのエクスポートを有効化の設定をオンにする必要があります。
次に、Dictionary API ソースのエクスポート設定の例を示します。この設定では、追加のセキュリティマーキングが付いていない Palantir 組織のデータを Dictionary API にエクスポートできます。

このシステムに実際にはデータをエクスポートしない場合でも、このソースへのエクスポートを有効化をオンにする必要があります。このシステムへの接続が開かれているコンピューティングジョブに Foundry のデータ入力を許可すると、データがエクスポートされる_可能性がある_ためです。
この例では、ハードコードされた固定のリストの代わりに、単語を含む入力データセットを使用します。また、レスポンスのステータスコードに基づく基本的なエラー処理も示します。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49from pandas import DataFrame from transforms.api import transform from transforms.api import Output, Input, TransformContext, transform_pandas from transforms.external.systems import external_systems, Source, ResolvedSource import pandas as pd import logging logger = logging.getLogger(__name__) @external_systems( dictionary_api_source=Source( "<source_rid>" ) ) @transform_pandas( Output("<output_dataset_rid>"), words_df=Input("<input_dataset_rid>"), ) def compute(dictionary_api_source: ResolvedSource, words_df: DataFrame) -> DataFrame: dictionary_api_url = dictionary_api_source.get_https_connection().url dictionary_api_client = dictionary_api_source.get_https_connection().get_client() words = words_df["word"].tolist() phonetics= [] for word in words: logger.info("Fetching word from api.dictionaryapi.dev: " + word) response = dictionary_api_client.get( dictionary_api_url + "/api/v2/entries/en/" + word ) if response.status_code == 200: data = response.json()[0] if "phonetic" in data: phonetic_transcription = data["phonetic"] else: logger.warning(f"No phonetic transcription found for {word}.") phonetic_transcription = None else: logger.warning(f"Request for {words} failed with status code {response.status_code}.") phonetic_transcription = None phonetics += [{"word": word, "phonetic": phonetic_transcription}] return pd.DataFrame(phonetics)
一般的なシステムを対象とした複雑なエンドツーエンドの例については、次のドキュメントを参照してください。
外部トランスフォームを使用する前に、Data Connection - 権限リファレンスページの内容をよく確認してください。
外部トランスフォームとレガシー外部トランスフォームのワークフローには、次のような主な違いがあります。
@use_external_systems() から @external_systems() に変更されました。外部トランスフォームの主な利点は次のとおりです。
現在、外部トランスフォームを外部トランスフォームに更新するための自動移行手段はありません。ただし、ほとんどのワークフローでは、必要な手動操作はわずかで済むと見込まれます。
外部トランスフォームに手動で移行するための主な手順は次のとおりです。
@external_systems() デコレーターをインポートして使用するようにコードを変更し、@use_external_systems() デコレーターのすべての使用箇所を削除します。多くの場合、トランスフォームのロジック内にある資格情報への参照を更新し、新たにインポートするソースから取得した資格情報を参照するようにする必要があります。トランスフォームには、外部トランスフォームとそのレガシーバージョンを混在させることはできません。これを解決するには、すべてのレガシー外部トランスフォームをソースベースの外部トランスフォームに移行するか(推奨)、トランスフォームを複数のトランスフォームに分割します。トランスフォームは、use_external_systems デコレーターを使用するものと、external_systems デコレーターを使用するものに分割できます。
lightweight 外部トランスフォーム外部トランスフォームは、シングルノード(軽量)のコンピュートエンジンにも対応しています。軽量コンピュートを使用すると、小規模および中規模のデータを処理するトランスフォームの実行速度を大幅に向上させることができます。
次の例は、@external_systems デコレーターとともに @lightweight デコレーターをトランスフォームに追加する方法を示しています。軽量トランスフォームの設定オプションの詳細については、Python トランスフォームのドキュメントを参照してください。
Copied!1 2 3 4@lightweight @external_systems( dictionary_api_source=Source("<source_rid>") )
より詳しい例については、Python でのソースを参照してください。
外部トランスフォームは、Agent プロキシポリシーを使用して、Data Connection の Agent 経由でオンプレミスおよびプライベートにホストされたシステムに接続することをサポートしています。
次の例では、Spark サイドカートランスフォームを使用して PostgreSQL データベースに接続する方法を示します。詳細については、Spark サイドカートランスフォームのドキュメントを参照してください。
次の例よりも単純なユースケースでは、Spark サイドカートランスフォームが不要な場合があります。Spark サイドカートランスフォームを必要としないほかの例は、このページの別の箇所にあります。
プライベートネットワークアクセスを使用する外部トランスフォームを設定するには、次の手順を実行します。
3. コードのインポートを有効化: コードインポート設定パネルで、このソースを Code Repositories にインポートできるようにするをオンにします。
4. トランスフォーム用のコードリポジトリをセットアップし、ソースをインポートします。
この例は、サイドカーコンテナ内のコンテナ化された Flask API を使用して、PostgreSQL データベースにクエリを実行する方法を示しています。
Copied!1 2 3 4 5 6 7 8FROM python:3.8-slim WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 1234 USER 5001 CMD ["python", "app.py"]
flask
psycopg2-binary
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31from flask import Flask, jsonify import psycopg2 app = Flask(__name__) @app.route('/tables', methods=['POST']) def tables(): data = request.get_json() password = data.get('password') connection_params = { "dbname": "<databasename>", "user": "<user>", "password": password, "host": "postgres.com", "port": "5432", } data = {"schema": [], "table": []} with psycopg2.connect(**connection_params) as conn: with conn.cursor() as cur: cur.execute( "SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'" ) tables = cur.fetchall() for schema, table in tables: data["schema"].append(schema) data["table"].append(table) return jsonify(data)
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18import requests from transforms.sidecar import sidecar from transforms.external.systems import external_systems, Source from transforms.api import transform_df, Output @external_systems( my_source=Source("<source_rid>") ) @sidecar(image="simple", tag="0.0.1") @transform_df( Output("<output_dataset_rid>"), ) def compute(my_source, ctx): password = my_source.get_secret("PASSWORD") response = requests.post("http://localhost:1234/tables", json={"password": password}) data = [(response.text,)] columns = ["table_schema_json"] return ctx.spark_session.createDataFrame(data, columns)
外部トランスフォームが外部ソースから大量のレコードを取得する場合、書き込み前にすべてのレコードをメモリーに保持すると、メモリー不足エラーが発生することがあります。次の BufferedParquetWriter クラスは、設定可能なメモリーしきい値を超えると、蓄積された行を定期的に Parquet ファイルにフラッシュします。このパターンは任意の外部データソースで使用でき、filesystem().open() で生のファイルを書き込み、put_metadata() でスキーマ推論を確定します。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95import gc import logging from datetime import datetime import pyarrow as pa import pyarrow.parquet as pq from pympler import asizeof from transforms.api import Output, transform, LightweightOutput from transforms.external.systems import external_systems, Source, ResolvedSource logger = logging.getLogger(__name__) class BufferedParquetWriter: """行をメモリー内のバッファに蓄積し、バッファのサイズが 設定可能なしきい値を超えると Parquet ファイルに書き出します。""" def __init__(self, output: LightweightOutput, schema: pa.Schema, flush_threshold_mb: int = 64): self.output = output self.schema = schema self.flush_threshold_bytes = flush_threshold_mb * 1024 * 1024 self.buffer = [] self.buffer_size_bytes = 0 self.file_index = 0 self.row_size_estimate = None def append(self, row: dict): """バッファに行を追加します。バッファがしきい値を超えた場合は、ディスクに書き出します。""" self.buffer.append(row) # 最初の行から行のサイズを推定し、以降はその推定値を使用します if self.row_size_estimate is None: self.row_size_estimate = asizeof.asizeof(row) self.buffer_size_bytes += self.row_size_estimate if self.buffer_size_bytes >= self.flush_threshold_bytes: self.flush() def flush(self): """バッファ内の行を Parquet ファイルに書き込み、バッファをクリアします。""" if not self.buffer: return self.file_index += 1 table = pa.Table.from_pylist(self.buffer, schema=self.schema) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"output_{timestamp}_{self.file_index}.parquet" with self.output.filesystem().open(filename, "wb") as f: pq.write_table(table, f) logger.info(f"Flushed {len(self.buffer)} rows to {filename}") self.buffer = [] self.buffer_size_bytes = 0 gc.collect() def finalize(self): """残りの行を書き出し、スキーマ推論を開始します。""" self.flush() if self.file_index > 0: self.output.put_metadata() # 出力レコードのスキーマを定義します OUTPUT_SCHEMA = pa.schema([ ("id", pa.string()), ("name", pa.string()), ("value", pa.float64()), ]) @external_systems( my_source=Source("<source_rid>") ) @transform.using( output=Output("<output_dataset_rid>"), ).with_resources( memory_gb=4, ) def compute(my_source: ResolvedSource, output: LightweightOutput): output.set_mode("replace") writer = BufferedParquetWriter(output, OUTPUT_SCHEMA, flush_threshold_mb=64) # 以下のループをデータ取得ロジックに置き換えます。 # たとえば、API のページ、データベースカーソルの行、 # またはメッセージキューのメッセージを順に処理します。 # append() を呼び出すたびにライターにデータが追加され、 # バッファがしきい値を超えると、自動的に Parquet ファイルに書き出されます。 for record in fetch_records_from_source(my_source): writer.append({ "id": record["id"], "name": record["name"], "value": record["value"], }) # バッファ内に残っている行を書き込み、スキーマ推論を完了します。 writer.finalize()
たとえば CSV 形式を要求する外部システムにファイルを提供するために、Foundry からデータを CSV としてエクスポートする必要がある場合は、入力データセットを読み取り、filesystem().open() を使用して出力に CSV ファイルを書き込む外部トランスフォームを使用できます。その後、出力データセットを Data Connection 経由でエクスポートできます。
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22import csv from transforms.api import Input, Output, transform, LightweightInput, LightweightOutput @transform.using( source_data=Input("<input_dataset_rid>"), csv_output=Output("<output_dataset_rid>"), ) def compute(source_data: LightweightInput, csv_output: LightweightOutput): OUTPUT_FILENAME = "export.csv" # 入力データセットを Polars DataFrame として読み込みます。 df = source_data.polars() fieldnames = df.columns # データを CSV ファイルとして書き込みます。 with csv_output.filesystem().open(OUTPUT_FILENAME, "w") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for row in df.iter_rows(named=True): writer.writerow(row)