‘Python’ カテゴリーのアーカイブ

suganuma

MCPサーバ開発を試してみた

MCPサーバをPython SDKを使って、作ってみました。

プロジェクトの作成

まずテンプレートからプロジェクト作成します。

uvx create-mcp-server
プロジェクト名等を聞かれるので、適当に入力します。プロジェクト作成後、指示に従って以下のコマンドを実行します。

cd <プロジェクト名>
uv sync --dev --all-extras

テンプレートプロジェクトの考察

作成されたファイル「src/test_notes/server.py」を見てみると、以下のデコレータを持つ関数があります。

  • @server.list_resources()
  • @server.read_resource()
  • @server.list_prompts()
  • @server.get_prompt()
  • @server.list_tools()
  • @server.call_tool()

resource、prompt、toolの一覧を配列で返すのと、項目の実行結果を返す関数のようです。

このMCPサーバをClaudeデスクトップとCline、Claudeコードの3つのMCPホストで使ってみました。
ClaudeデスクトップとClineはJSONファイルによって設定します。Claudeコードは、以下のような感じで設定します。

claude mcp add-json test-notes '{"command":"uv","args":["--directory","/path/to/test-notes","run","test-notes"]}'
MCPホスト起動時にMCPサーバはバックグラウンドプロセスとして起動します。なおMCPホスト終了時にMCPサーバのプロセスは終了しません。その後にMCPホストを起動すると、二重にMCPサーバのプロセスが起動しています。
MCPサーバ起動時にtool等の一覧取得してるようです。

このMCPサーバはプロパティに名前と内容を持つノートを追加する機能です。
toolは、3つのMCPホストで利用できましたが、resourceとpromptは、Claudeデスクトップでしか利用できませんでした。ドキュメントを見てみると、ClineはpromptがNGでresourceはOK、ClaudeコードはpromptがOKでresourceはNGと書いてあるので、多分やり方が分からなかっただけだと思います。

なお、ドキュメントのクイックスタートでは、上記の実装方法とはちょっと違っていて、デコレータを作るクラスがServerではなく、FastMCPになっています。
FastMCPを使うと、Pythonの型ヒントとdocstringを使用してツール定義を自動生成するので、一覧を返す関数を作る必要がなくなります。関数のコメントから一覧を生成し返してくれます。
ただ、実際にFastMCPを使ってMCPサーバを作ってみましたが、ClaudeデスクトップとClaudeコードでは正常動作しましたが、ClineではMCPサーバ接続時にListToolsRequestが失敗し、うまくいきませんでした。ただ調べてみるとissueに上がっているので、そのうちClineでもできるようになると思います。

SSH接続するMCPサーバを作ってみる

Clineを使って作っていきます。LLMはclaude-sonnet-3.7です。

src/ssh_connect/server.pyはMCPサーバのサンプルコードです。
これを参考に、MCPでSSH接続できるコードを作って。
ホスト名等の設定は、MCP設定のenvで設定するようにして。
これだけで、ほぼ完成しました。
以下のtoolを作成してくれました。

  • connect
  • disconnect
  • execute
  • upload
  • download
  • list_files

paramikoを使ってSSHしています。頼んでもないのにsftpでファイルアップロード・ダウンロード・ファイル一覧のツールも作ってくれました。

ただ実際にClineで実行してみると、ClineはこのMCPの使い方が分からず、使えませんでした。
以下のプロンプトで修正依頼しました。

Clineで実行したら、以下のように使い方が分からないと言われた。

The user wants to establish an SSH connection. Based on the environment details, I can see that we're in a directory that appears to be an SSH connection tool for MCP (Model Context Protocol). The directory structure shows files like server.py in the src/ssh_connect/ directory and an example file ssh_example.py in the examples/ directory.
I should first understand what this SSH connection tool does and how to use it. Let me examine some of the key files to understand how to proceed.
First, let's look at the README.md file to get an overview of the project

毎回、README.mdを見ることなく、使えるようにしたい。
その結果、ツール一覧を返す関数を修正し、各ツールにdescriptionを追加してくれました。
追加した各ツールのdescriptionは、VSCのClineのMCP Serversに表示されていました。


これでClineはMCPサーバ起動してListToolsRequestすれば、このMCPサーバの使い方がわかるようになりました。
以下、実行結果です。

User:SSH接続して。設定は環境変数から取得して。
Assistant: Successfully connected to the SSH server at 192.168.1.35 on port 22 with username lanuser. The connection is now established and ready for use.
Is there anything specific you'd like me to do with this SSH connection? For example, I can:

Execute commands on the remote system
List files in a directory
Upload or download files
Take notes about what we find

User:実行してるプロセス一覧を表示して。
Assistant:現在のシステムで実行されているプロセスの一覧を表示しました。主なプロセスは以下のようになっています:

システム関連プロセス:

/sbin/init(PID 1)- システムの最初のプロセス
systemd-journald、systemd-udevd、systemd-resolved などのシステムサービス
カーネルスレッド([kthreadd]、[kworker]など)


ネットワーク関連:

NetworkManager(PID 369)
wpa_supplicant(PID 429)- 無線ネットワーク
sshd(PID 1425)- SSHサーバー


グラフィック関連:

Xorg(PID 1829)
gdm3(PID 1279)- GNOMEディスプレイマネージャ
gnome-shell(PID 2715)


ハードウェア関連:

nvargus-daemon(PID 1235)
nvfancontrol(PID 1038)- ファン制御
bluetoothd(PID 1034)- Bluetooth管理


コンテナ関連:

dockerd(PID 1617)
containerd(PID 1261)


ユーザーセッション:

現在のSSHセッション(PID 3458、3502)
bash(PID 3352)

システムは複数のユーザー(root、gdm、lanuser など)でプロセスを実行しており、GNOME環境が稼働していることが分かります。また、Docker関連のプロセスも実行されているため、コンテナ技術が使用可能な状態です。
作成したコードはこれです。
なぜかテストコード、インストールのシェルやHTMLのガイドまで作ってくれました。

MCPクライアントを作ってみる

これもClineで作ります。

@https://modelcontextprotocol.io/quickstart/client
上記を参考にpythonでMCPサーバに接続できるチャットボットを作って。
クライアントだけでなく、テスト用のMCPサーバを作成してくれましたが、
テスト用のMCPサーバが正常動作しなかったので、以下のプロンプトで修正をお願いしました。

@https://modelcontextprotocol.io/quickstart/server
のMCPサーバの作り方を見ると、test_server.pyとだいぶ違うようです。
FastMCPを使って作り直して。
テスト用のMCPサーバで動作確認後、先程作ったSSH接続のMCPサーバで確認します。

uv run client.py - --directory ~src/samples/test-mcp-server/ssh-connect run ssh-connect --env SSH_HOST=192.168.1.35 --env SSH_PORT=22 --env SSH_USERNAME=lanuser --env SSH_PASSWORD=****
結果、ほぼうまくいきましたが、時々何故か以下のようなエラーが出ます。

'ToolUseBlock' object has no attribute 'text'
機会があれば、MCPクライアントをSlackで作ってみたいと思います。

suganuma

量子アニーリングでシフト表作成

某雑誌で量子アニーリングの記事を読み興味を持ち、これを使ったなにか良いお題がないか考えた結果、シフト表を作成しようと思い立ちました。

以下のような月のシフト表に、自動で出勤日を決定できるようにします。
0が休日で、1が出勤日とします。

フィックスターズの量子アニーリングクラウド「Fixstars Amplify」を使います。
Pythonでamplifyモジュールをインストールするだけで簡単に利用できます。

今回使うイジングマシンのエンジンは、Fixstars Amplify Annealing Engine です。これはGPUベースのアニーリングマシン(いわゆる古典コンピュータ)です。
これ以外にも、たとえば「D-Wave 2000Q」や「D-Wave Advantage」といった量子コンピュータが使えます。同じコードで設定を変えるだけで、簡単にエンジンを切り替えられます。

量子アニーリングのプログラミングは、だいたい以下のステップです。

  1. 決定変数を定義
    決定変数は2値の値を取りえます
  2. 決定変数を使い、条件を最適化関数として記述
    イジングマシンは最適化関数が最小になるような決定変数の組み合わせを自動で見つけてくれます
  3. イジングマシンを実行
    決定変数の値が確定
  4. 結果(確定した決定変数)の処理

シフト表作成で、以下の条件を課します。

  • できるだけ全員が同じ出勤数にする
  • 一日あたり二人が出勤
  • 連続6勤務以上は禁止
  • 担当者A:どの日でもOK
  • 担当者B:土日のみ出勤可
  • 担当者C:土日以外のみ出勤可
  • 担当者D:1日〜5日は休み

Google Colabノートブックを使って作業を行いました。
以下のリンクでノートブックを閲覧できます。
https://colab.research.google.com/drive/1xMYavIDCyFJoxkvzwhxIZnrUxMk4d1wA?usp=sharing

作成したシフト表が以下です。

条件を満たしたシフト表を作成することができました。

suganuma

M5StickVで独自のモデルを使って画像認識

M5StickVを入手したので、独自のモデルを作成し画像認識を試してみました。
V-Training を利用するのが一番お手軽ですが、別の方法を試してみました。

M5StickVではkmodelというフォーマットのモデルを使います。
TensorFlow Liteのモデル(tflite)からMaix Toolboxを使ってkmodelに変換できます。

最初にGCPのAutoML Visionを使ってEdge modelを作成し、tfliteモデルをダウンロードしてkmodelに変換しようとしましたが、以下のエラーが発生しました。

Fatal: expect FLOAT32 tensor but got UINT8, use '--inference_type=FLOAT' when converting via toco.
AutoML Vision Edgeでは、「QUANTIZED_UINT8」の8bit量子化モデルになりますが、それだと駄目らしいです。

次に、Keras組み込みのMobileNet v1を使って転移学習を試してみましたが、kmodel変換時に以下のエラーが出ました。

Layer Pad is not supported: Only paddings of [[0,0],[1,1],[1,1],[0,0]] is supported
パディングに関して、なにか違うみたいです。

結局ネットで調べて、K210で利用可能な学習済みのMobileNet v1をダウンロードしました。
kmodel作成に関して色々制限があるようです。

以下の方針でモデル作成しました。

  • 開発はGoogle Colaboratoryを利用
  • MobileNet v1の転移学習
  • 学習データはgoogleの画像検索から取得
  • 分類は以下の3つ 狛犬、アイボ、柴犬

kmodelを作成したGoogle Colabノートブックが以下です。
https://colab.research.google.com/drive/1aEM8gkJJPnrtLGYR3yDdsWydU01lq9Y9

上記ページは編集・実行不可ですが、「ファイル」>「ドライブにコピーを保存…」 すると各自のGoogleアカウントのGoogleドライブにipynbファイルが保存され、編集・実行ができるようになります。

作成したファイル「model.kmodel」と以下のファイル「boot.py」をSDカードにコピーし、M5StickVに挿して起動します。
画像認識しaccuracyが0.9以上の場合に、異なる色のライトを点灯するようにしました。

アイボを認識すると、赤く点灯

boot.py

import lcd
import image
import time
import uos
import sys
import sensor
import KPU as kpu
import os

fm.register(board_info.BUTTON_B, fm.fpioa.GPIO2)
but_b = GPIO(GPIO.GPIO2, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

#fm.register(board_info.LED_W, fm.fpioa.GPIO3)
#led_w = GPIO(GPIO.GPIO3, GPIO.OUT)
#led_w.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_R, fm.fpioa.GPIO4)
led_r = GPIO(GPIO.GPIO4, GPIO.OUT)
led_r.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_G, fm.fpioa.GPIO5)
led_g = GPIO(GPIO.GPIO5, GPIO.OUT)
led_g.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_B, fm.fpioa.GPIO6)
led_b = GPIO(GPIO.GPIO6, GPIO.OUT)
led_b.value(1) #RGBW LEDs are Active Low


lcd.init(freq=15000000)
lcd.rotation(2) #Rotate the lcd 180deg

sensor.reset() # Reset and initialize the sensor. It will
# run automatically, call sensor.run(0) to stop
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # Set frame size to QVGA (320x240)
sensor.skip_frames(time = 2000) # Wait for settings take effect.
sensor.set_windowing((224, 224)) # zoom in on the center of the lens
clock = time.clock() # Create a clock object to track the FPS.


lcd.clear()
lcd.draw_string(70, 20, "aibo:Red")
lcd.draw_string(70, 40, "siba:Green")
lcd.draw_string(70, 60, "koma:Blue")
labels = ["aibo", "siba", "koma"]
leds = [led_r, led_g, led_b]
task = kpu.load("/sd/model.kmodel")

while(True):
clock.tick()
img = sensor.snapshot()
fmap = kpu.forward(task, img)
plist = fmap[:]
pmax = max(plist)
max_index = plist.index(pmax)
fps =clock.fps()
lcd.display(img)
print("%s (%.2f), fps:%2.1f" % (labels[max_index], pmax, fps))
for i, led in enumerate(leds):
if i == max_index and pmax > 0.9:
led.value(0)
else:
led.value(1)

suganuma

kaggleを試してみた

最近機械学習の勉強をはじめました。何冊か本を購入しザーッと目を通し(深くは理解できてません)、次に何か適当なサンプルで機械学習を試してみようと思いましたが、なかなか都合が良いデータというのはないです。
そんな時にKaggleを見つけました。Kaggleはデータ分析コンペを行うサイトですが、機械学習を勉強するのにも、すごく役立つサイトです。

「Titanic: Machine Learning from Disaster」という学習用のコンペをチュートリアルで最初にやるようになってますが、すでに日本語でたくさん紹介されてるので「House Prices: Advanced Regression Techniques」というのをやってみます。タイタニックが分類に対してこれは回帰の問題です。

やってみる、といっても初心者には何からやるのか検討がつきませんが、Kaggleには多くの方々が自分が行ったコード等(Kernels)を説明付きで公開してくれているので、それらを真似すればできます。
最初は人が作ったKernelsをひたすら見て自分のものにする、という感じかと思います。
以下も基本、人が作ったKernelsを参考に書いてます。

また、Kaggleには開発環境もあり、スクリプトまたはノートブック(Jupyter Notebook)の形式でブラウザから試すことできます。システムとしてよくできていると思います。

目的

アイオワ州のエイムズにある住宅価格を79の特徴量より予測します。Ames Housing dataset

データの前処理

ライブラリを読み込みます。

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
from scipy.stats import skew
from scipy.stats.stats import pearsonr
%config InlineBackend.figure_format = 'retina' #set 'png' here when working on notebook
%matplotlib inline

データを確認してみます。

train = pd.read_csv("../input/train.csv")
test = pd.read_csv("../input/test.csv")
train.head()

IdMSSubClassMSZoningLotFrontageLotAreaStreetAlleyLotShapeLandContourUtilitiesLotConfigLandSlopeNeighborhoodCondition1Condition2BldgTypeHouseStyleOverallQualOverallCondYearBuiltYearRemodAddRoofStyleRoofMatlExterior1stExterior2ndMasVnrTypeMasVnrAreaExterQualExterCondFoundationBsmtQualBsmtCondBsmtExposureBsmtFinType1BsmtFinSF1BsmtFinType2BsmtFinSF2BsmtUnfSFTotalBsmtSFHeating…CentralAirElectrical1stFlrSF2ndFlrSFLowQualFinSFGrLivAreaBsmtFullBathBsmtHalfBathFullBathHalfBathBedroomAbvGrKitchenAbvGrKitchenQualTotRmsAbvGrdFunctionalFireplacesFireplaceQuGarageTypeGarageYrBltGarageFinishGarageCarsGarageAreaGarageQualGarageCondPavedDriveWoodDeckSFOpenPorchSFEnclosedPorch3SsnPorchScreenPorchPoolAreaPoolQCFenceMiscFeatureMiscValMoSoldYrSoldSaleTypeSaleConditionSalePrice0160RL65.08450PaveNaNRegLvlAllPubInsideGtlCollgCrNormNorm1Fam2Story7520032003GableCompShgVinylSdVinylSdBrkFace196.0GdTAPConcGdTANoGLQ706Unf0150856GasA…YSBrkr85685401710102131Gd8Typ0NaNAttchd2003.0RFn2548TATAY0610000NaNNaNNaN022008WDNormal2085001220RL80.09600PaveNaNRegLvlAllPubFR2GtlVeenkerFeedrNorm1Fam1Story6819761976GableCompShgMetalSdMetalSdNone0.0TATACBlockGdTAGdALQ978Unf02841262GasA…YSBrkr1262001262012031TA6Typ1TAAttchd1976.0RFn2460TATAY29800000NaNNaNNaN052007WDNormal1815002360RL68.011250PaveNaNIR1LvlAllPubInsideGtlCollgCrNormNorm1Fam2Story7520012002GableCompShgVinylSdVinylSdBrkFace162.0GdTAPConcGdTAMnGLQ486Unf0434920GasA…YSBrkr92086601786102131Gd6Typ1TAAttchd2001.0RFn2608TATAY0420000NaNNaNNaN092008WDNormal2235003470RL60.09550PaveNaNIR1LvlAllPubCornerGtlCrawforNormNorm1Fam2Story7519151970GableCompShgWd SdngWd ShngNone0.0TATABrkTilTAGdNoALQ216Unf0540756GasA…YSBrkr96175601717101031Gd7Typ1GdDetchd1998.0Unf3642TATAY035272000NaNNaNNaN022006WDAbnorml1400004560RL84.014260PaveNaNIR1LvlAllPubFR2GtlNoRidgeNormNorm1Fam2Story8520002000GableCompShgVinylSdVinylSdBrkFace350.0GdTAPConcGdTAAvGLQ655Unf04901145GasA…YSBrkr1145105302198102141Gd9Typ1TAAttchd2000.0RFn3836TATAY192840000NaNNaNNaN0122008WDNormal250000 

データの変換

ほとんどのモデルは特徴量及び出力がガウス分布に従っている方がうまくいきます。ヒストグラムが左右対称の「ベルカーブ」になるように、特徴量及び出力にlogを適用します。

matplotlib.rcParams['figure.figsize'] = (12.0, 6.0)
prices = pd.DataFrame({"price":train["SalePrice"], "log(price + 1)":np.log1p(train["SalePrice"])})
prices.hist()

出力「SalePrice」はlog(price + 1)で、歪度がだいぶ減りました。1を足してるのは、データに値 0 があるので(そして対数 は0に対して定義できないので)、直接logを使うことはできないためです。

train["SalePrice"] = np.log1p(train["SalePrice"])

特徴量で、数値でかつ歪度が大きい場合のみ同様の変換を行います。

all_data = pd.concat((train.loc[:,'MSSubClass':'SaleCondition'],
test.loc[:,'MSSubClass':'SaleCondition']))
# 数値のみ
numeric_feats = all_data.dtypes[all_data.dtypes != "object"].index

# 欠損値NaNを除外後、歪度を計算
skewed_feats = train[numeric_feats].apply(lambda x: skew(x.dropna()))
skewed_feats = skewed_feats[skewed_feats > 0.75]
skewed_feats = skewed_feats.index

all_data[skewed_feats] = np.log1p(all_data[skewed_feats])

カテゴリ変数の変換特徴量がカテゴリ文字列のデータを、回帰で扱えるようするため数値に変換します。ワンホットエンコー ディングという方法を使い、カテゴリ変数を1つ以上の0と1の値を持つ新しい特徴量で置き換えます。
たとえばGarageQualという特徴量は、Ex(Excellent)、Gd(Good)、TA(Typical/Average)、Fa(Fair)、Po(Poor)、NA(No Garage)のいづれかの値を持ちますが、新たに以下の0または1の値を持つ特徴量を作ります。

GarageQual_Ex、GarageQual_Gd、GarageQual_TA、GarageQual_Fa、GarageQual_Po、GarageQual_NA。

というのを以下の一行でやってくれます。

all_data = pd.get_dummies(all_data)

欠損値を平均値で埋めるall_data = all_data.fillna(all_data.mean())
モデル作成特徴量の中にはあまり重要でないものがあるようなので、L1正則化を行うLasso回帰でモデルを作ります。L1正則化を行うと、いくつかの係数は無視されます。

X_train = all_data[:train.shape[0]]
X_test = all_data[train.shape[0]:]
y = train.SalePrice

from sklearn.linear_model import Lasso
model_lasso = Lasso()
scores = cross_val_score(model_lasso, X_train, y) # 交差検証で評価
scores.mean()

0.54583131173476207デフォルトだとスコア低いです。Lassoのパラメータalphaをいろいろ変えて最適な値を見つけます。
というのを、交差検証を用いて最適なalphaを探してくれるLassoCVがやってくれます。

from sklearn.linear_model import LassoCV
model_lasso = LassoCV(alphas = [1, 0.1, 0.001, 0.0005]).fit(X_train, y)
scores = cross_val_score(model_lasso, X_train, y) # 交差検証で評価
scores.mean()

0.90117979437604168どの特徴量が影響を及ぼしているか確認してみます。

coef = pd.Series(model_lasso.coef_, index = X_train.columns)
imp_coef = pd.concat([coef.sort_values().head(10),
coef.sort_values().tail(10)])
matplotlib.rcParams['figure.figsize'] = (8.0, 10.0)
imp_coef.plot(kind = "barh")
plt.title("Coefficients in the Lasso Model")

GrLivAreaとは、地上の建物面積です。
Above grade (ground) living area square feet

テストデータを予測

テストデータを予測し、expを適用し元に戻します。

y_test_pred = model_lasso.predict(X_test)
y_test_pred = np.exp(y_test_pred)-1

提出ファイル「submission.csv」を作成submission = pd.DataFrame({"Id": test["Id"],"SalePrice": y_test_pred})
submission.loc[submission['SalePrice'] <= 0, 'SalePrice'] = 0
fileName = "submission.csv"
submission.to_csv(fileName, index=False)
あとは、outputタブを開き、「Submit to Competition」ボタンを押すと、ファイルがサブミットされ、Score計算が行われ、結果がLeader board上で表示されます。

murave

IronPythonとジェネリックと初期値わたし

C#勉強会に行って自己紹介で「MVVMパターン用のフレームワーク使ってViewとViewModelはC#、ModelはIronPythonで書います」と言ったら微妙な空気になりました、夏。

muraveです。

というわけでC#とIronPythonを組み合わせて書いてます。基本的に配列や辞書は便利なのでPythonのモノ使ってましたがパフォーマンス・チューニングやっててジェネリック使ったほうがいいところもあるかな、と。

IronPython側でのジェネリックの書き方は知っててたまに使ってたんですが(C#での<>が[]になります)、試したら普通に初期値も与えられるのにちょっと感激。

C#で書くとList<string>の場合

>>> import site
>>> import clr
>>> from System.Collections import Generic as DotNetCollectionsG

>>> dot_l = DotNetCollectionsG.List[str]()
>>> print dot_l
List[str]()

>>> dot_l = DotNetCollectionsG.List[str](['a', 'b'])
>>> print dot_l
List[str](['a', 'b'])
C#で書くとDictionary<string, int>の場合

>>> import site
>>> import clr
>>> from System.Collections import Generic as DotNetCollectionsG

>>> dot_dict = DotNetCollectionsG.Dictionary[str,int]()
>>> print dot_dict
Dictionary[str, int]()

>>> dot_dict = DotNetCollectionsG.Dictionary[str,int]({'a':1 ,'b':1})
>>> print dot_dict
Dictionary[str, int]({'b' : 1, 'a' : 1})
ね♪

helpみたら普通に書いてありました。

>>> help(dot_dict)
Help on Dictionary[str, int] object:

class Dictionary[str, int](object)
| Dictionary[str, int]()
| Dictionary[str, int](capacity: int)
| Dictionary[str, int](comparer: IEqualityComparer[str])
| Dictionary[str, int](capacity: int, comparer: IEqualityComparer[str])
| Dictionary[str, int](dictionary: IDictionary[str, int])
| Dictionary[str, int](dictionary: IDictionary[str, int], comparer: IEqualityComparer[str])
|
以下略

えらいなぁ。

murave

IronPythonでもRequestsが使いたい

Requests: 人間のためのHTTP
http://ja.python-requests.org/en/latest/

Pythonの便利なHTTPライブラリ Requests を IronPython でも使いたいわけです。
こんなことを書いているということは素直には使えないわけです。

一応動作してるかな〜、というところまで行けたのでメモしときます。IronPython は 2.7.3 で、Requests は 1.1.0 です。

ポイントは2つ。

まず一つ目。

sys._getframe が必要なので ipy.exe (or ipy64.exe) に -X:Frames オプションを付けて起動する。

オプションはこんな感じで確認できます。

C:\Users\murave>ipy -help
Usage: ipy.exe Usage: ipy [options] [file.py|- [arguments]]
<前略>
-X:Frames Enable basic sys._getframe support
<後略>

2つ目。IronPython には idna エンコーディングがないので回避する。

requests の models.py で使用されているので取り敢えずコメントアウト。

idnaで検索すると290行目あたりで使用されてます。

# try:
# netloc = netloc.encode('idna').decode('utf-8')
# except UnicodeError:
# raise InvalidURL('URL has an invalid label.')

netloc = netloc.encode(‘utf-8’).decode(‘utf-8’) に書き換えてみたりもしたのですが馬鹿らしい気がするのでコメントアウトしました。
おそらくこの影響で「ドメインとURLの国際化」は未対応になってしまってますが、動かないよりはいいかと。

C:\Users\murave>ipy -X:Frames
IronPython 2.7.3 (2.7.0.40) on .NET 4.0.30319.18010 (32-bit)
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import json
>>> r = requests.get('http://172.16.191.1:8001/api/v1_0/poll/')
>>> r.text
u'{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_cou
nt": 3}, "objects": [{"id": 1, "pub_date": "2013-03-05T13:51:15", "question": "W
hat\'s up?", "resource_uri": "/api/v1_0/poll/1/"}, {"id": 2, "pub_date": "2013-0
3-06T10:01:35", "question": "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8", "resource_ur
i": "/api/v1_0/poll/2/"}, {"id": 3, "pub_date": "2013-03-06T13:51:15", "question
": "\u3044\u3044\u3044", "resource_uri": "/api/v1_0/poll/3/"}]}'
>>> res = r.json()
>>> print res['objects'][1]['question']
日本語テスト
>>> payload = {"pub_date": "2013-03-08T20:19:15", "question": "どうかね?"}
>>> r = requests.post('http://172.16.191.1:8001/api/v1_0/poll/', data=json.dumps(payload), headers={"Content-Type": "application/json"})
>>> r
>>> payload = {"pub_date": "2013-03-08T20:19:15", "question": "putは?"}
>>> r = requests.put('http://172.16.191.1:8001/api/v1_0/poll/1/', data=json.dumps(payload), headers={"Content-Type": "application/json"})
>>> r
>>> r = requests.delete('http://172.16.191.1:8001/api/v1_0/poll/1/')
>>> r

と、requests.get、requests.post、requests.put、requests.delete 動いているようです。

murave

YAPC::Asia 2012 LTソン muraveスライド&デモ動画

スライド

Yapc2012ltthon from Junya Murabe

デモ動画

PerlイベントでMobiRubyからIronPythonにつなぐネタをやって比較的スベった感じ。

YAPC::Asia 2012 楽しかった! LTはスベったけど!