Skip to main content

A fast, Flask-inspired Python web framework with a Rust HTTP core and SSR.

Project description

higuma

CI PyPI Documentation Python License

公式ドキュメント: higuma.moyashi.xyz

Rust 製 HTTP コアと Flask に近い Python API を組み合わせた、SSR 対応の軽量 Web フレームワークです。

from higuma import Higuma

app = Higuma(__name__)


@app.get("/")
def index():
    return app.render_template("index.html", title="higuma")


@app.get("/users/<int:user_id>")
def user(user_id: int):
    return app.jsonify(user_id=user_id)


if __name__ == "__main__":
    app.run()

プロジェクトの状態

現在のバージョンは 0.1.0 です。Web アプリケーションを作るための主要機能は揃っていますが、 公開直後の API であり、第三者によるセキュリティ監査や大規模な本番実績はまだありません。 本番導入時は「本番運用」の項目も確認してください。

higuma は WSGI/ASGI アダプターではなく、axum と Tokio を利用する HTTP サーバーを 自身で起動します。

特徴

  • Rust の axum/Tokio による HTTP 受付、ルート照合、リクエスト制限
  • Flask に近い @app.get()@app.post()Blueprint API
  • <int:id><uuid:id><path:name> を含む動的ルーティング
  • RequestResponse、JSON、redirect、ファイルレスポンス
  • MiniJinja を Rust 側で描画する SSR
  • before/after hooks、独自 middleware、エラーハンドラ
  • ETag、条件付き GET、Range に対応した静的ファイル
  • 署名付き Cookie セッション
  • CORS、セキュリティヘッダ、Trusted Host middleware
  • async def ハンドラと非同期 lifecycle hooks
  • 外部サーバー不要のテストクライアント
  • アプリ起動とルート一覧を扱う CLI
  • Python 3.10 以降向けの型情報

アーキテクチャ

Client
  |
  v
axum / Tokio (Rust)
  |  HTTP、ルーティング、HEAD/OPTIONS、body size limit
  v
Python request pipeline
  |  middleware -> before_request -> view -> after_request
  v
Response / TemplateResponse / FileResponse
  |
  v
Rust response encoder / MiniJinja

ネットワーク処理やルート照合は Rust で行います。Python の view function を実行する部分は CPython の GIL の影響を受けるため、すべての処理が Rust だけで完結するわけではありません。

必要環境

  • Python 3.10 以上
  • Rust stable
  • Rust が利用できる C/C++ リンカー

Windows では Visual Studio Build Tools の MSVC、Linux では GCC/Clang、macOS では Xcode Command Line Tools が利用できます。

インストール

PyPI からインストールします。

python -m pip install higuma

最新の開発版をリポジトリからインストールする場合:

git clone https://github.com/Madoa5561/higuma.git
cd higuma
python -m pip install -U pip
python -m pip install -e .

開発ツールも含める場合:

python -m pip install -e ".[dev]"

wheel を作る場合:

python -m pip install maturin
maturin build --release

生成物は target/wheels/ に出力されます。

クイックスタート

app.py:

from higuma import Higuma

app = Higuma(__name__)


@app.get("/")
def index():
    return "<h1>Hello from higuma</h1>"


@app.post("/api/echo")
def echo(request):
    return app.jsonify(received=request.json)


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8000)

起動:

python app.py

CLI でも起動できます。

higuma run app:app --host 127.0.0.1 --port 8000

ブラウザで http://127.0.0.1:8000/ を開きます。

ルーティング

@app.route("/items", methods=["GET", "POST"])
def items(request):
    ...


@app.get("/items/<int:item_id>")
def item(item_id: int):
    return {"item_id": item_id}


@app.put("/items/<int:item_id>")
def update_item(request, item_id: int):
    return {"item_id": item_id, "payload": request.json}

利用できる converter:

Converter Python 型
string / str <string:name><name> str
int <int:item_id> int
float <float:price> float
uuid <uuid:object_id> uuid.UUID
path <path:filename> / を含む str

path converter はルートの最後にだけ配置できます。末尾 / の有無は同じルートとして 扱われます。

GET ルートには HEAD が自動で割り当てられ、OPTIONS は Allow ヘッダとともに自動応答します。

URL の生成

@app.get("/users/<int:user_id>")
def profile(user_id: int):
    ...


url = app.url_for("profile", user_id=42)
# /users/42

Request

ハンドラは引数なしでも、request 引数付きでも定義できます。

@app.post("/upload/<path:name>")
def upload(request, name: str):
    print(request.method)
    print(request.path)
    print(request.args)
    print(request.headers)
    print(request.body)
    print(request.text)
    print(request.path_params)

Flask に近いグローバル proxy も利用できます。

from higuma import request


@app.get("/search")
def search():
    return {"query": request.args.get("q", "")}

request context の外で request にアクセスすると RuntimeError になります。

Query string

request.argsMultiDict です。

# /search?tag=rust&tag=python&page=2
tags = request.args.getlist("tag")
page = request.args.get("page", 1, type=int)

主なメソッド:

  • get(key, default=None, type=None)
  • getlist(key, type=None)
  • items(multi=False)
  • to_dict(flat=True)

JSON

@app.post("/api/items")
def create_item(request):
    payload = request.json
    return {"name": payload["name"]}, 201

Content-Type が JSON ではない場合は 415 Unsupported Media Type、不正な JSON は 400 Bad Request になります。判定を緩める場合は request.get_json(force=True, silent=True) を利用できます。

Form と Cookie

name = request.form.get("name")
session_id = request.cookies.get("session_id")

application/x-www-form-urlencoded に対応しています。multipart form-data は現時点では 未対応です。

Response

view function は次の値を返せます。

return "HTML string"
return b"binary"
return {"ok": True}
return ["one", "two"]
return "created", 201
return {"ok": True}, 201, {"x-request-id": "abc"}
return Response("custom", status=202)

利用できる response class:

  • Response
  • HTMLResponse
  • PlainTextResponse
  • JSONResponse
  • RedirectResponse
  • FileResponse
  • TemplateResponse

JSON

from higuma import jsonify

return jsonify({"items": items})
return app.jsonify(items=items, total=len(items))

Redirect

from higuma import redirect

return redirect("/login")
return redirect("/new-location", status=308)

Cookie

response = app.make_response("ok")
response.set_cookie(
    "token",
    "value",
    secure=True,
    httponly=True,
    samesite="Lax",
)
return response

削除:

response.delete_cookie("token")

ファイル送信

from higuma import send_file

return send_file("reports/monthly.pdf")
return send_file(
    "exports/data.csv",
    as_attachment=True,
    download_name="data.csv",
)

ユーザー入力からファイルパスを直接組み立てないでください。公開ディレクトリ配下を安全に配信する 場合は組み込みの static route を利用します。

SSR とテンプレート

higuma は MiniJinja を使い、テンプレートを Rust 側で描画します。基本構文は Jinja2 に近い ものです。

templates/index.html:

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
  </head>
  <body>
    <h1>{{ heading }}</h1>
    {% for item in items %}
      <p>{{ item }}</p>
    {% endfor %}
  </body>
</html>

Python:

app = Higuma(__name__, template_folder="templates")


@app.get("/")
def index():
    return app.render_template(
        "index.html",
        title="higuma",
        heading="Rust powered SSR",
        items=["fast", "small", "typed"],
    )

全テンプレートに値を追加する context processor:

@app.context_processor
def shared_context():
    return {"app_name": "higuma"}

グローバルの render_template() も利用できますが、context processor を適用する場合は app.render_template() を利用してください。

静的ファイル

デフォルトではアプリケーションルートの static//static/ に割り当てられます。

app = Higuma(
    __name__,
    static_folder="public",
    static_url_path="/assets",
)

組み込み static route は次に対応します。

  • MIME type の推測
  • path traversal の防止
  • ETag と If-None-Match
  • Cache-Control
  • 単一 byte range
  • Last-Modified

cache 時間:

app.config["STATIC_CACHE_MAX_AGE"] = 86400

静的配信を無効にする場合:

app = Higuma(__name__, static_folder=None)

大規模な本番配信では CDN またはリバースプロキシから配信することを推奨します。

エラー処理

from higuma import NotFound, abort


@app.get("/admin")
def admin():
    abort(403, "administrator only")


@app.errorhandler(403)
def handle_forbidden(error):
    return {"error": error.detail}, 403


@app.errorhandler(NotFound)
def handle_not_found(error):
    return app.render_template("404.html", path=request.path), 404

組み込み例外:

  • BadRequest (400)
  • Unauthorized (401)
  • Forbidden (403)
  • NotFound (404)
  • MethodNotAllowed (405)
  • Conflict (409)
  • RequestEntityTooLarge (413)
  • UnsupportedMediaType (415)
  • TooManyRequests (429)
  • InternalServerError (500)

debug=True では未処理例外の traceback がレスポンスに含まれます。本番では有効にしないで ください。

Hooks と middleware

before / after request

@app.before_request
def authenticate(request):
    if request.path.startswith("/admin"):
        ...


@app.after_request
def add_request_id(request, response):
    response.headers["x-request-id"] = request.state["request_id"]
    return response

before hook が response を返すと view function は実行されません。

独自 middleware

import time


@app.middleware
def timing(request, call_next):
    started = time.perf_counter()
    response = app.make_response(call_next(request))
    response.headers["server-timing"] = (
        f"app;dur={(time.perf_counter() - started) * 1000:.2f}"
    )
    return response

後から登録した middleware ほど内側で実行されます。

CORS

from higuma import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://example.com"],
    allow_methods=["GET", "POST"],
    allow_headers=["content-type", "authorization"],
    allow_credentials=True,
)

credential を許可する場合、origin に * を使わないでください。

セキュリティヘッダ

from higuma import SecurityHeadersMiddleware

app.add_middleware(
    SecurityHeadersMiddleware,
    content_security_policy="default-src 'self'; img-src 'self' data:",
)

Trusted Host

from higuma import TrustedHostMiddleware

app.add_middleware(
    TrustedHostMiddleware,
    ["example.com", "*.example.com"],
)

署名付きセッション

from higuma import SessionMiddleware

app.add_middleware(
    SessionMiddleware,
    secret_key="replace-with-a-long-random-secret",
    secure=True,
)


@app.get("/counter")
def counter(request):
    request.session["count"] = request.session.get("count", 0) + 1
    return {"count": request.session["count"]}

セッションデータは署名されますが暗号化はされません。パスワード、token、個人情報を保存しないで ください。Cookie サイズを超える大量のデータにも適していません。

Blueprint

from higuma import Blueprint

api = Blueprint("api", __name__, url_prefix="/api")


@api.get("/health")
def health():
    return {"status": "ok"}


app.register_blueprint(api)

endpoint は api.health になります。

app.url_for("api.health")

非同期ハンドラ

@app.get("/remote")
async def remote_data():
    result = await some_async_operation()
    return {"result": result}

async def は利用できますが、現在の Rust/Python bridge は Python coroutine を同期 boundary で待機します。大量の長時間接続を扱う純粋 ASGI フレームワークと同じ実行モデルではありません。 CPU-bound 処理は process pool や job queue に分離してください。

Lifecycle

@app.on_startup
async def connect():
    ...


@app.on_shutdown
async def disconnect():
    ...

Config

app.config.from_mapping(
    DEBUG=False,
    STATIC_CACHE_MAX_AGE=86400,
)

app.config.from_object("settings.ProductionConfig")
app.config.from_file("config.json")
app.config.from_envvar("HIGUMA_CONFIG")
app.config.from_prefixed_env("HIGUMA")

from_prefixed_env()HIGUMA_DEBUG=false のような値を JSON として解釈できる場合は JSON 型に変換します。

主な初期値:

Key Default
DEBUG False
TESTING False
MAX_CONTENT_LENGTH 8 * 1024 * 1024
STATIC_CACHE_MAX_AGE 3600
SERVER_HEADER higuma/<version>

MAX_CONTENT_LENGTHSERVER_HEADER は Rust core 作成時に固定されるため、constructor の max_content_length を使って設定してください。

CLI

アプリ起動:

higuma run app:app
higuma run package:create_app --host 0.0.0.0 --port 8000 --workers 4

factory function は引数なしで呼び出されます。

ルート一覧:

higuma routes app:app

バージョン:

higuma --version

HIGUMA_APP=app:app を設定すると CLI の app 引数を省略できます。

テスト

def test_health():
    client = app.test_client()
    response = client.get("/health")

    assert response.status_code == 200
    assert response.json == {"status": "ok"}

利用できるメソッド:

  • client.get()
  • client.post()
  • client.put()
  • client.patch()
  • client.delete()
  • client.head()
  • client.options()
  • client.open()

JSON、form、query:

client.post("/items", json={"name": "book"})
client.post("/login", data={"name": "higuma"})
client.get("/search", query={"q": "rust"})

セッション Cookie は同じ client 内で保持されます。

プロジェクト自身のテスト:

python -m unittest discover -s tests -v

本番運用

最低限、次を行ってください。

  • debug=False にする
  • TLS 終端、接続制限、request buffering を行うリバースプロキシを前段に置く
  • TrustedHostMiddleware を設定する
  • CORS と Content Security Policy をアプリごとに見直す
  • 強い session secret と HTTPS 用の secure Cookie を使う
  • static file は可能なら CDN へ分離する
  • timeout、ログ収集、監視、process restart を外部 supervisor で管理する

workers は Tokio worker thread 数であり、Python process 数ではありません。複数 CPU core で Python view を並列化する場合は、OS の process manager または container replicas を使い、 異なる port/socket に複数 process を配置してください。

パフォーマンス

ベンチマーク:

python -m pip install flask
python bench/compare.py

この script はローカルの単純な GET /ping を比較する開発用 smoke benchmark です。 Flask 開発サーバーとの比較は本番構成同士の公平な比較ではなく、結果は CPU、OS、Python、 client、handler 内容で大きく変化します。

性能を評価するときは、自分の application handler、同じ TLS/proxy 構成、同じ worker 数、 latency percentile、error rate を含めて測定してください。

higuma が高速化する主な範囲:

  • HTTP connection と body の処理
  • ルート照合
  • SSR template rendering
  • HEAD/OPTIONS と一部 static response

Python handler、database、外部 API、serialization が支配的な workload では改善幅は小さく なります。

現在の制約

0.1.0 では次は未対応です。

  • WebSocket
  • multipart file upload
  • WSGI / ASGI mount
  • OpenAPI 自動生成
  • template bytecode cache
  • built-in database / ORM
  • built-in multi-process supervisor

これらはアプリケーションごとに外部ライブラリで補うか、今後のバージョンで追加します。

プロジェクト構成

higuma/
├── src/lib.rs                 # Rust HTTP core
├── python/higuma/             # Python public API
├── tests/                     # integration tests
├── examples/                  # runnable SSR example
├── bench/                     # local benchmark harness
├── .github/workflows/         # CI and wheel builds
├── pyproject.toml             # Python package metadata
└── Cargo.toml                 # Rust crate metadata

開発

python -m pip install -e ".[dev]"
cargo fmt --all -- --check
cargo check
python -m ruff check python tests examples
python -m unittest discover -s tests -v

詳しくは CONTRIBUTING.md、セキュリティ報告は SECURITY.md、変更履歴は CHANGELOG.md を参照してください。

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

higuma-0.1.0.tar.gz (62.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

higuma-0.1.0-cp310-abi3-win_amd64.whl (1.1 MB view details)

Uploaded CPython 3.10+Windows x86-64

higuma-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl (1.5 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

higuma-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

higuma-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

higuma-0.1.0-cp310-abi3-macosx_11_0_arm64.whl (1.2 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

higuma-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file higuma-0.1.0.tar.gz.

File metadata

  • Download URL: higuma-0.1.0.tar.gz
  • Upload date:
  • Size: 62.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for higuma-0.1.0.tar.gz
Algorithm Hash digest
SHA256 04c13338583368662d5f2a7268aabd318d2e9e1b2ffaeeeffd4fd4532920875b
MD5 3415e900098d189d678a8b877687c0d1
BLAKE2b-256 731bac7dbb3e2054ce4db72802534dafe96e3e82a8750e0135a9518485210c0d

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0.tar.gz:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: higuma-0.1.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for higuma-0.1.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7f7af8103ef7bd671561a3a6685a8d85c3fabf59534fc99dffc185c59f8a82e9
MD5 292cc1eafe7cec0d799c72587217e625
BLAKE2b-256 6483121729d6b21d14634d0134810d85301e0b9dc5176c0feb9069e5ff7a3645

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for higuma-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f98f4aa21cd9c56bceb05debe80e406967d026cd3cf8ee04666dac237c4242ea
MD5 8988710d37f2577afe7d167f033c38f2
BLAKE2b-256 e00a14f6405ebae540e1b43c09d71cb02cb1d8d4613aec70d0f7582de92deef9

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for higuma-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 27e69dd1fcebe9c3299979dbf64433ade429875700edd11554860fdebae3155f
MD5 5c3d0837609826f94472d861d5416f7b
BLAKE2b-256 bd4e36ab036b83d5742e0bed8aa235303a49730b9933f11bcc88afe5dba0807b

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for higuma-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 1a155edc4120d8256f335fc8204cdb7359473736af730adf41108b0446077c00
MD5 bbc9f9b610d865e296d69b8b58714473
BLAKE2b-256 03a023367f9824e46977b94229440c8b666a19aa3e8270ff5b327234f65f5925

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for higuma-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0c8a72c2c649f7367b00c74298c5c6924e7712862ed81865346a7201f92324f6
MD5 8a1f2950dc1a02ad9bebc3a2145ecef5
BLAKE2b-256 b528efd1a67eb0637bdb39736a8ffd834310d229b4e2fc6b44065f87478164ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file higuma-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for higuma-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 3fcb908b2321a046ca5e360ffe2429337bc12c703e9c93a053f62067eeab4792
MD5 ac63ea582e843646ee08c1149f1a4162
BLAKE2b-256 b3e42475b36630064b6767393801175badeee29c582a30f476bd82a48d3fc344

See more details on using hashes here.

Provenance

The following attestation bundles were made for higuma-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on Madoa5561/higuma

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page