Microsoft Graph で Microsoft Todo の CRUD

プライベートで使っている Todo アプリを、Todoist から Microsoft Todo に移行しようかと検討中。もし移行するとなると、リストやタスクを入力し直すのは面倒だ。移行用にプログラム書きたいところ。

Microsoft Todo なら、Microsoft Graph という RESTful API を使えば CRUD できそうだった。SDK もあるところは流石。

docs.microsoft.com

Python ではあるけど、Microsoft Graph を使って Microsoft Azure の情報を取得する記事はあった。

dev.classmethod.jp

自分の場合は C# だし、Microsoft Azure には既にサインアップ済なので、Azure Active Directoy にアプリを登録するところからスタート。

注意点として、一般に公開するアプリの場合は、「サポートされているアカウントの種類」を「任意の組織ディレクトリ内のアカウント(任意の Azure AD ディレクトリ - マルチテナント)と個人の Microsoft アカウント(SkypeXboxなど)」にしておく必要がありそうだった。

.NET 用の SDK は NuGet からインストール。

www.nuget.org

OAuth で使うパッケージも併せてインストール。

www.nuget.org

Microsoft Todo のリストとタスクを CRUD するサンプルを書いてみた。

using Azure.Identity;
using Microsoft.Graph;

// Microsoft Todo のタスクとリストを CRUD するには
// Tasks.ReadWrite の許可が必要。
var scopes = new[]
{
    "Tasks.ReadWrite",
};

// 一般ユーザーの Microsoft Todo にアクセスするときは
// common を指定すれば良いみたい。
var tenantId = "common";

// Azure AD で登録したアプリケーションの ID (Client ID)
var clientId = "YOUR_CLIENT_ID";

// Web ブラウザを表示して、
// ユーザーに Microsoft アカウントにログインしてもらえるよう、
// GraphServiceClient を構成する。
// 最初に API を呼び出すタイミングでブラウザが起動する。
var options = new InteractiveBrowserCredentialOptions
{
    TenantId = tenantId,
    ClientId = clientId,
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
    RedirectUri = new Uri("http://localhost"),
};
var interactiveCredential = new InteractiveBrowserCredential(options);
var graphClient = new GraphServiceClient(interactiveCredential, scopes);

// リスト一覧表示
var todoLists = await graphClient.Me
    .Todo
    .Lists
    .Request()
    .GetAsync();
foreach (var todoList in todoLists)
{
    Console.WriteLine($"既存のリスト:{todoList.DisplayName}");
}

// リスト登録
var createdList = await graphClient.Me
    .Todo
    .Lists
    .Request()
    .AddAsync(new TodoTaskList
    {
        DisplayName = "Test List",
    });
Console.WriteLine($"登録したリスト:{createdList.DisplayName}");

// タスク登録
var createdTask = await graphClient.Me
    .Todo
    .Lists[createdList.Id]
    .Tasks
    .Request()
    .AddAsync(new TodoTask
    {
        Title = "Test Task",
    });
Console.WriteLine($"登録したタスク:{createdTask.Title}");

// リスト内のタスク一覧表示
var tasks = await graphClient.Me
    .Todo
    .Lists[createdList.Id]
    .Tasks.Request()
    .GetAsync();
foreach (var task in tasks)
{
    Console.WriteLine($"{createdList.DisplayName}:{task.Title}");
}

// タスク更新
createdTask.CompletedDateTime = DateTimeTimeZone.FromDateTimeOffset(DateTimeOffset.Now);
var updatedTask = await graphClient.Me
    .Todo
    .Lists[createdList.Id]
    .Tasks[createdTask.Id]
    .Request()
    .UpdateAsync(createdTask);
Console.WriteLine($"更新したタスク:{updatedTask.Title}");

// タスク削除
await graphClient.Me
    .Todo
    .Lists[createdList.Id]
    .Tasks[updatedTask.Id]
    .Request()
    .DeleteAsync();
Console.WriteLine($"削除したタスク:{updatedTask.Title}");

// リスト更新
createdList.DisplayName = "Test Updated List";
var updatedList = await graphClient.Me
    .Todo
    .Lists[createdList.Id]
    .Request()
    .UpdateAsync(createdList);
Console.WriteLine($"更新したリスト:{updatedList.DisplayName}");

// リスト削除
await graphClient.Me
    .Todo
    .Lists[updatedList.Id]
    .Request()
    .DeleteAsync();
Console.WriteLine($"削除したリスト:{updatedList.DisplayName}");

Console.ReadLine();

.NET 6 で実行。自分個人の Microsoft アカウントで試してみた。

まだたいしたリストは登録してなかったけど、一応自分のリスト一覧が表示できていた。CRUD も成功。ただ、SDKAPI にクセがあったな。

レストラン 花の木

記念日に、大濠公園内にある「レストラン花の木」にランチを食べにいった。子どもが産まれたり、コロナ禍になったりで、しばらく足が遠のいていたので、かなり久しぶり。新しい建物になってからは初めてだ。

スープをオニオングラタンスープに変えたかったので、Bコースを予約しておいた。

アミューズはヤングコーンのフリットがサクサクで特に美味。

前菜のイカは程よい歯応え。ニラのソースが夏らしい味わいだった。

お目当てのオニオングラタンスープ。黄金色のコンソメが美味い。牛肉で出汁をとった後、さらにミンチを加える、2段仕込みらしい。チーズもたっぷりでアツアツ。初めて食べたけど、かなり気に入った。

メインは牛肉のステーキ。赤身の肉は大好物だ。シンプルに肉が美味い。肉汁を使ったシンプルなソースがそれを一層引き立てている。

デザートのヨーグルトアイスは不覚にも写真撮り忘れてしまった。

食後のコーヒーとお茶菓子。カヌレなんて食べたの久しぶり過ぎる。

福岡市内のフレンチは色々行ったけど、今のところ、花の木が1番自分達の口に合っている。実際、イベント事では一番利用している。子どもも気に入ったみたいで、また来たい、次はいつ、と気が早い。

レストラン 花の木
〒810-0051 福岡県福岡市中央区大濠公園1-3 ボートハウス大濠パーク
14,000円(平均)7,000円(ランチ平均)
r.gnavi.co.jp

自分 v0.39.0 リリース

また一つ歳を重ねてしまった。30代ラストイヤーに突入。親が39歳のとき自分は13歳で、39歳はだいぶ大人だと思ったものだけど、いざ自分がなってみるとそうでもない。

フレッシュさはとうの昔に失われた自覚あるけど、だからといって老け込んだ気はしない。今でもマンガは読むし、ゲームで遊ぶし、仕事ではコード書いてるし。やってることが新人時代から変わらない。結婚してるし、子どもも産まれているしと、環境は学生時代から変化してるが。それでも変わらないものはあるってことか。よほどのことがない限り、このまま変わらず続いていきそうな気がする。良くも悪くも。

よほどのこと、例えば転職なんががそうだけど、積極的に動いたりしていない。現状に満足というわけでも無いけど。福岡市内もしくはフルリモートで働けて、給料が上がって、かつ関わりたいプロダクトがある会社でもあれば考えなくもない、という程度。そんなのまず無い。そういえば、天神ビジネスセンタービルに Google が拠点を開設、って話はどうなったんだろうね。あれから続報聞かないな。

転職よりは、個人開発で一発当ててアーリーリタイアしたい。ずっと言ってる気がする。アーリーリタイアしてやることと言ったら、旅行して、美味いもの食べて、あとは趣味でコード書いて。結局コードは書くので、アーリーリタイアじゃなくて、自分のプロダクトで暮らせるようになれたら良いや。理想は ropross 氏。

個人開発は、先月 MAUI が GA したので、本腰を入れ直した。自分が欲しいアプリを作っていて、プロトタイプの段階だけど、なかなか良い感じ。少なくとも、自分は使い続けるだろうな、と思えるものが出来そう。公開するなら、マネタイズも考えないといけないな。広告は嫌いだから、サブスクが理想。月額500円とか払ってもらえる価値を提供できるか。考えることは多い。だけど、それが楽しかったりする。

かぐや様は告らせたい(26)

かぐやと早坂が長年下準備をしていたとはいえ、四宮家相手に生徒会役員たちで、かぐやを奪還できたことに驚いた。上手く行きすぎ感があるが、それすら演出。一時的に三男の雲鷹の力を借りたが、もとは藤原書記の発案なわけで。次男の青龍を丸め込んだミコといい、今回は皆優秀だった。

会長は「かぐやを迎えに行く」を有言実行。かぐやも、友人たちを信じられるようになって、人を好きになって、好きな人に好きになってもらえるような人間になれた証を、会長からの告白によって手に入れることができてよかった。

そして最後の最後にタイトル回収。鳥肌たった。

OpenAPI の YAML および JSON から PDF を生成

はじめに

OpenAPI の YAMLJSON から、openapi-generator-cli を使って、リファレンスを HTML や Markdown を出力したりしている。

ただ、リファレンスを PDF で配りたいという要望もあり、どうしたものかと思案中。

openapi-generator-cli だけでは、直接 PDF は出力できないけど、いったん AsciiDocに出力することで、AsciiDoc から PDF を作れそうだった。

OpenAPI Spec から AsciiDoc

OpenAPI サンプルの定番、Petstore の YAML から AsciiDoc を出力。

openapi-generator-cli generate -i petstore.yaml -g asciidoc -o dest

AsciiDoc から PDF

AsciiDoc から PDF を作成するには、asciidoctor-pdf を使う。Ruby の gem なので、Ruby の環境が必要。

Windows なら、Ruby Installer でインストールするのが手っ取り早い。

rubyinstaller.org

そして asciidoctor-pdf もインストール。執筆時のバージョンは 2.0.17 だった。

gem install asciidoctor
gem install asciidoctor-pdf

組み込みの日本語フォントでよければ、次のコマンドで日本語の PDF を生成できた。

asciidoctor-pdf -a scripts=cjk -a pdf-theme=default-with-fallback-font dest\\index.adoc -o petstore.pdf

おわりに

10MB 超あるお化け swagger.json でも試してみたところ、メモリ 1GB 弱使いながら、かなりの時間をかけて、なんとか PDF 出力できた。

本格的に体裁を整えるとしたら、asciidoc ジェネレーターのカスタムテンプレートと asciidoctor-pdf のテーマを作ることになるだろうな。やりたくない。

NumSharp で損失関数

「ゼロから作る Deep Learning」を読んで、C# でゼロから作ってみる続き。MNIST データセットを使ったニューラルネットの実装は、配布されているパラメーターのファイルが Pickle 形式だったのでスキップ。損失関数に進むことにした。

NumSharp を使って、2乗和誤差と交差エントロピー誤差、2つの損失関数を実装してみた。

using NumSharp;

{
    // 正解を 2 とする
    var t = new double[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 };

    // 例1: 「2」の確率が最も高い場合 (0.6)
    var y = new double[] { 0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0 };
    Console.WriteLine(mean_squared_error(np.array(y), np.array(t)).ToString());

    // 例2: 「7」の確率が最も高い場合 (0.6)
    y = new double[] { 0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0 };
    Console.WriteLine(mean_squared_error(np.array(y), np.array(t)).ToString());
}

{
    var t = new double[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 };
    var y = new double[] { 0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0 };
    Console.WriteLine(cross_entropy_error(np.array(y), np.array(t)).ToString());

    y = new double[] { 0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0 };
    Console.WriteLine(cross_entropy_error(np.array(y), np.array(t)).ToString());
}

Console.ReadLine();

// 2乗和誤差
static NDArray mean_squared_error(NDArray y, NDArray t)
{
    return 0.5 * np.sum((y - t) * (y - t), NPTypeCode.Double);
}

// 交差エントロピー誤差
static NDArray cross_entropy_error(NDArray y, NDArray t)
{
    var delta = 1e-7;
    return (-1) * np.sum(t * np.log(y + delta), NPTypeCode.Double);
}

.NET 6 で実行。

(y - t)^2 みたいに書きたかったけど、NDArray は ^ 演算子オーバーロードしていなかったので、同じ値を掛けて代用。なかなか NumPy そのままとはいかないな。

服を着るならこんなふうに(10)

これまでに登場したキャラ各々を話の中心に、福岡や京都や仙台や北海道などのショップを紹介するという、今までとだいぶ毛色が違う巻だった。

福岡市住んでるけど、紹介されたショップには立ち寄れないな…。畏れ多くて。京都や仙台、北海道には行きたい。ただ、旅行に行って立ち寄るのは、ショップはショップでも飲食店。花より団子だなぁ。