カタベログ

IT技術に関するブログを書きたい.食べ物関連はInstagramをご参照の事.

Pythonでエクスポーネンシャル・バックオフを実装した

でも使われることはなかったのでブログで供養する。 エクスポーネンシャル・バックオフとは、APIリクエストなどが失敗した際のリトライの方法の一種。 リトライ周期についてリトライ回数が増えるにつれて指数関数的にインターバルが増加する仕組み。

import time
from functools import wraps
from random import uniform
from typing import Any, Callable

def backoff(max_retry=5, max_wait=30.0, target_except=Exception):
    """リトライ処理デコレーター

    エクスポーネンシャル・バックオフという理論に基づき、装飾された関数にて例外が発生し続ける間、繰り返し実行する。
    ただし、最大リトライ回数、または最大待ち時間以上待とうとする場合は例外を再スローする。
    なお、最大リトライ回数、または最大待ち時間のうち先に到達した方を優先して処理をする。
    例えば、待ち時間が15秒だとしてもリトライ回数が5回を上回ったらそこで例外を再スローする。

    Args:
        max_retry (int, optional): 最大リトライ回数。既定は5回。
        max_wait (int, optional): 最大待ち時間。既定は30秒。
        target_except (_type_, optional): リトライ対象とする例外。既定は全ての例外クラス。
    """

    def backoff_wrapper(func: Callable[..., Any]):
        @wraps(func)
        def wrapper(*args, **kwargs):
            sleep_time_accumulation = 0.0

            for count in range(max_retry):
                try:
                    result = func(*args, **kwargs)
                    break
                except target_except as e:  # pylint: disable=broad-exception-caught
                    if count + 1 >= 5:
                        raise e

                    jitter = uniform(-1, 5)
                    sleep_time = 2.0**count + jitter
                    sleep_time_accumulation += sleep_time

                    if sleep_time_accumulation >= max_wait:
                        raise e

                    time.sleep(sleep_time)

            return result

        return wrapper

    return backoff_wrapper

ぼくのかんがえたさいきょうのVSCodeでのPython開発環境

序文

※ 2023/02/22(Wed.) 推奨拡張にisortの拡張が漏れていたので加筆しました

Pythonを初めて触ったのは筆者が19歳のとき……プログラミングに目覚め始めた頃、そのときに出会ったRubistの方への反骨精神で使い始めました。 あれから月日が流れ、結婚、出産、育児、マイホーム・マイカー購入といった人生一大イベントたちをことごとく無視して生きてきて、遂にお仕事でPythonを本格的に使う日が来ました(やったー)。

筆者はWebアプリケーションエンジニアとしてはJavaScript/TypeScriptの経験が長いです(といっても2、3年ですが)。 その中でリンターやフォーマッターといった自動でいい感じのコードを書くための技術に触れ、それらのVSCodeでの設定の方法を学んできました。 そして今回Pythonを扱うとなって調べてみると、Pythonもその手のツールは当然のように存在しております。 そうなると、VSCodeに慣れ親しんだ筆者としては「VSCodeでJS/TSを書いていた時のような感覚でPythonを書きたい」と思うのが極めて自然ですよね。

ということで、自分がVSCodeに設定しているPython関連の設定内容と推奨拡張を記載しつつ、途中ちょっとハマったところもあったのでその事例を共有できたらなって思います。

なお、PyCharm使えばいいじゃんとかそういう意見は求めてません。悪しからず。

続きを読む

一部で話題のスリープソートを実装してみた

 きっかけ

実行結果

%  node SleepSort.js
Target:
[
   9.001279948037155,   7.814290804058153,
  5.6203515898400225,    8.88900237341443,
   6.284693239942214,   7.678417600633072,
  4.3539035833889645,  3.4782778608417475,
   0.631035744901749,   5.799741583572917,
  0.1913970742445037, 0.43133450479187374,
  5.9727079749091345,  0.7017940380951004,
   4.175168145227373,  3.9140154382038905,
  0.7652450971294433,   9.143441457849157,
  2.7689060541041854,   8.731233419192447
]
Result:
[
  0.1913970742445037, 0.43133450479187374,
   0.631035744901749,  0.7017940380951004,
  0.7652450971294433,  2.7689060541041854,
  3.4782778608417475,  3.9140154382038905,
   4.175168145227373,  4.3539035833889645,
  5.6203515898400225,   5.799741583572917,
  5.9727079749091345,   6.284693239942214,
   7.678417600633072,   7.814290804058153,
   8.731233419192447,    8.88900237341443,
   9.001279948037155,   9.143441457849157
]

実装

TimerといえばsetTimeoutでお馴染みのJavaScriptですよね。 ということで、最近Promise版のsetTimeoutの話もよく見かけるのでJavaScriptで書きました。

const { setTimeout } = require("timers/promises");
​
const sleepSort = async (numberList) => {
  const sortedList = [];
  const appendList = (element) => {
    sortedList.push(element);
  };
​
  const timerList = numberList.map(async (element) => {
    return setTimeout(element * 1000, element).then(() => {
      appendList(element);
    });
  });
​
  await Promise.all(timerList).catch((error) => {
    console.error(error);
  });
​
  return sortedList;
};
​
const main = () => {
  const length = 20;
  const offset = 10;
​
  const target = Array(length)
    .fill(offset)
    .map((element) => {
      return Math.random() * element;
    });
  console.log("Target:");
  console.log(target);
​
  sleepSort(target).then((result) => {
    console.log("Result:");
    console.log(result);
  });
};
​
main()

Next.jsにTerserを入れてる状態でAPI Routesを使う際に気をつけなければならない事

TL;DR

  • Terserのdrop_consoleを使わずにNext CompilerのremoveConsoleを使いましょう
    • そうしないとバックエンド側のconsole.logprocess.stdout.writeも消えます
続きを読む

Next.jsのAPI RoutesでOpenAPIを使ってIF定義を作り、その定義をAspidaで使って型安全にAPIを使う

背景

NextJSを使う機会に恵まれました。

仕事で1からフロントエンドとバックエンドの技術選定ができるチャンスが到来しました。これまでVue 2.xをゴリゴリ触る機会があったものの、誰かが作ったもののエンハンスやメンテナンスばかりだったので、Next.jsやNuxt.jsといったパワフルかつフロントエンドとバックエンド両方を(やろうと思えば)対応できるフレームワークを使おうと決心しました。モノレポの真似事ができるし。Nuxt.jsを使うなら3系を使いたかったですが、まだベータ版なので本番利用は憚られます。そのため、Next.jsを選ぶに至りました。

ベースはNext.jsにするとして一つ気になることがありました。

作って終わりではなく長く運用されていくものなので、インターフェース(以下IF)資料と実装が1対1になるOpenAPIを使いたいです。複雑なIFではないのでOpenAPIで作成しても支障はなさそうでした。Next.jsのAPI Routesは、connect形式やExpress形式のmiddlewareであれば利用できるとありました。それに合致するOpenAPIのmiddlewareはどうやらexpress-openapi-validatorのようでした。でも、API Routes with express-openapi-validatorな実装記事はどこにも見つかりませんでした。

ただ、お客様が触るシステムである以上、作って終わりな実装をするのはエンジニアとして許せないので、とにかくAPI Routes with OpenAPIを実現しようとなりました。

TL;DR

  • express-openapi-validateを用いることでAPI RoutesでもOpenAPIを使ったバリデーションを行えることが分かった
  • どうしてもAPI Routesだと共通処理の実装が難しく複雑になりがちだと分かった
    • 簡単かつ短いコードでAPIを実装できるメリットの裏返しなので一長一短
    • API実装規模に応じてAPI Routesを使うか否かは判断すべきだというのがよく分かった
  • Aspidaを使うことでフロントのAPIリクエストに型の恩恵を受けることができるのは開発体験が良いことが実感できた
    • VSCodeを使って開発した結果ではあるが、おそらくWebStromでも同じ体験ができることだろう

謝辞

本件実装にあたり、短納期ながら他業務の合間を縫って技術検証とサンプルおよび本実装までしてくださったSさん、ありがとうございました。

続きを読む

ViteをVue2のプロジェクトに入れてみた

背景

2021年末、関係者たちが次々冬休みに入る中、年長者なのでしんがりを買って出た。 一部コミュニケーションのミスもあったりで、結果としては1日2日多く休めたはずだった。 打ち合わせもなければメンバからの相談事もPRレビューもなく、かといって面倒臭い仕事もやる気もしない。 そんな気分の折、メンバの一人がTypeScriptでツールを作成していたことに触発されて、1日つかって新しい技術を組み込んでみようと思い立ったのであった。

モチベーション

  • ローカルビルドやHMRの時間を短縮して開発効率を上げる
    • Vue2のままViteを導入する
    • テストがないのでVite関連では極力コードに手を入れない

ちなみにViteが何かは日本語ドキュメントもあるので以下をチェック。

ja.vitejs.dev

  • 背景
  • モチベーション
  • やり方
    • 詰まったところ
      • ただのViteは入れられない
      • node_modulesの外から組み込んでるライブラリが読み込めない
      • veturとの連携によってエラーやらサジェスチョンやら出る
      • pluginの仕組み上、生成されるコンフィグがviteの全てのコンフィグを使えるようになっていない
      • モジュールは全てES Module(ESM)にしないと動かない
      • 本番ビルドにVue2向けプラグインは対応していない
      • 環境変数読み込みの違いのせいでvue-cliによるビルドが失敗する
  • 結果として早くなったのか?
続きを読む

Prismaを使ってみて分かった事と、ここ最近感じたプロジェクト開発での教訓的なもの

備忘録的な側面が多分にある。 みんなが興味ありそうなPrismaの話は頭に持ってきたので、それだけ読んで閉じてもらってもいいと思う。

  • HerokuとPrismaの相性があまり良くない
    • publicスキーマしか実質使えない
    • Heroku Connectとの相性が悪い
  • テスト
    • コントローラのテストをするならサービスはモック化した方が良い
    • DBを繋いだ単体テストにおいてはテストデータの管理がとても重要
    • テストコードがある安心感
  • 設計
    • 新しい技術を使うのはほどほどに
    • 「共通部品は必要になったら都度作ろう」
  • プロジェクト運営
    • 少人数開発でもコミュニケーション不足は容易く起きる

HerokuとPrismaの相性があまり良くない

publicスキーマしか実質使えない

HerokuでDBを扱うとなると、Add-onでHeroku Postgresを使うことになる。 Heroku Postgresをアプリに追加すると、環境変数にDATABASE_URLが追加される。 schema.prismaにenv(DATABASE_URL)と記載すると、publicスキーマに対して定義が可能になる。 ここでDBにスキーマを新たに切ったとしたとき、環境変数と文字列を結合した文字列をenv()の引数に渡したい。 だが現状、引数に当たる箇所で演算はできない。

Heroku Connectとの相性が悪い

Heroku ConnectでSalesforceと繋いだ時にSalesforceスキーマが作成される。 Salesforce上の(カスタム)オブジェクトをHeroku Postgresに同期させる事ができる。 つまり、SalesforceスキーマにあるテーブルはSalesforce上で管理される。 Prismaで管理するテーブルとSalesforceで管理するテーブルがあり、そして(当然)プログラムからはその両方を扱わねばならない。 だがローカル環境で試験を行うにはローカルDBにSalesforceで管理するテーブルがないと試験できないので、Prismaスキーマは作らないといけない。 Salesforceスキーマだけは必ず二重管理になってしまうので相性が悪いと思う。

続きを読む