Skip to main content

🚀 fastship-py

# app.fastship.jp テンプレートから作ったリポジトリの backend/ (pyproject.toml のあるところ) で実行する
uv sync
uv run fastship sync                   # 規約を導入する。fastship-py を更新したら再実行する (何度実行してもよい)
uv run fastship add module billing     # モジュールの雛形を足す
backend/
├── pyproject.toml       # ruff / mypy / deptry のプロジェクト固有の設定もここに書く
├── fastship.ruff.toml   # 🔄 ruff の規約 (pyproject.toml の [tool.ruff] が extend する)
├── .importlinter        # 🔄 アーキテクチャ規約 (import-linter)
└── src/
    ├── core.py          #    全モジュールを束ねる Backend (テンプレートにある)
    └── billing/         # 🌱 fastship add module が作るモジュール
        ├── core.py      #    DI 登録・購読者・テーブル定義の宣言点 (AppModule)
        ├── application/
        ├── domain/model/
        ├── exception/
        └── port/adapter/{persistence,resource,service}/
  • 🔄 規約: fastship が管理する。fastship sync のたびに最新へ上書きする (手で書き換えても戻る)
  • 🌱 雛形: fastship add module が作る。以後はプロジェクトのもので、fastship sync も触らない

src 直下のモジュールは uv sync で editable インストールされる (テンプレートの pyproject.toml の [build-system])。 そのため ruff / mypy / deptry / lint-imports / pytest / alembic のどれも、PYTHONPATH なしでプロジェクトルートから実行できる。

📚 How To

💾 Persistence

💾 テーブルクラス ( SQLAlchemy ) の実装

〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver.users_table_row.py

from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

# NOTE: テーブルクラスは `DataBase` を継承して作ること
from fastship.common.port.adapter.persistence.sqlalchemy import DataBase


class UsersTableRow(DataBase):
    __tablename__ = "users"

    id: Mapped[str] = mapped_column(String(36), primary_key=True)
    name: Mapped[str] = mapped_column(String(50), index=True)  # index 名は ix_users_name

〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver.__init__.py

from .users_table_row import UsersTableRow
# 他のテーブルクラス

テーブルクラスを足したら、そのモジュールの tables にドライバーモジュールのパスを足す

class 〇〇(AppModule):
    @property
    @override
    def tables(self) -> list[str]:
        return ["〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver"]
🚚 マイグレーション (Alembic)

事前にマイグレーション用のパッケージを生成

uv add alembic
uv run alembic init migration

migration/env.py:

# alembic init の生成物から、target_metadata = None をこの 3 行に差し替える

from fastship.common.port.adapter.persistence.sqlalchemy import DataBase
from core import Backend  # 全モジュールを束ねた CompositeModule

target_metadata = DataBase.metadata_of(Backend())
🔀 リードレプリカへの読み書き振り分け (SQLAlchemy)

接続先はインフラが環境変数で注入する (Secret Manager の値は実行基盤で環境変数として公開する)。アプリ側では設定しない。

環境変数 内容
DATABASE_WRITER_URL プライマリ。必須
DATABASE_READER_URL リードレプリカ (Read Pool のエンドポイント)。未設定ならすべてプライマリへ送る
from fastship.common.port.adapter.persistence.sqlalchemy import SQLAlchemyUnitOfWork

unit_of_work = SQLAlchemyUnitOfWork.from_settings(pool_pre_ping=True)

トランザクション (unit_of_work.session()) は、読み取りも含めてすべてプライマリで実行する。 レプリカの遅延した値を元に更新すると、プライマリの新しい値を上書きしてしまう (lost update) ため。 更新系の生 SQL (text()) もここで実行する。

トランザクション外の読み取り (unit_of_work.query()) は、以下の規則でリーダーとライターに振り分ける。

クエリ 送り先
読み取り (select() / session.get() / text()) リーダー
書き込み (flush / insert() / update() / delete()) ライター
SELECT ... FOR UPDATE (with_for_update()) ライター
一度でもライターへ送ったセッションの以降のクエリ ライター (書き込み直後に古いデータを読まない)
session.use_writer() を呼んだ後のクエリ ライター (レプリカ遅延を許容できない読み取り)
with unit_of_work.query() as session:
    session.use_writer()
    session.get(...)

認可の判定 (無効化された API キー、停止されたユーザー、取り消された権限など) は、レプリカ遅延の間に 古い状態で許可しないよう、session.use_writer() を呼んでプライマリから読むこと。

Read Pool のノード数を増減しても、エンドポイントは変わらないためアプリ側の変更は要らない。 SessionPreparer (RLS の SET LOCAL など) は、振り分け先のコネクションそれぞれに適用される。

🛠️ Other

📦 DI コンテナ

登録方法:

from sqlalchemy import create_engine

from fastship.common.application import UnitOfWork
from fastship.common.port.adapter.persistence.inmem import InMemUnitOfWork
from fastship.common.port.adapter.persistence.sqlalchemy import SQLAlchemyUnitOfWork
from fastship.di import DI, DIContainer

engine = create_engine("postgresql+psycopg://...")

DIContainer.instance().register(
    DI.of(UnitOfWork, {"InMem": InMemUnitOfWork()}, SQLAlchemyUnitOfWork(engine)),
)

利用方法:

from fastship.di import DI, DIContainer

# `DI_FOR_PY=InMem` なら InMemUnitOfWork のインタンスを取得
# `DI_FOR_PY=` なら SQLAlchemyUnitOfWork のインタンスを取得
unit_of_work = DIContainer.instance().resolve(UnitOfWork)
from injector import inject


class 〇〇ApplicationService:
    @inject
    def __init__(self, 〇〇_repository: 〇〇Repository) -> None: ...
from fastship.common.domain.model import DomainRegistry

〇〇_repository = DomainRegistry.resolve(〇〇Repository)

✏️ Style Check

CI でも検査の前に fastship sync を実行する。コミット済みの設定ではなく、ロックした fastship-py の規約で検査するため

uv run fastship sync
uv run ruff format --check . && uv run ruff check . && uv run mypy && uv run deptry src && uv run lint-imports

fastship sync で差分が出たら (fastship-py を更新した / モジュールを足した) そのままコミットする。

🪄 lint 規約 (ruff / mypy)

fastship sync が管理するのは次の 2 つ

  • fastship.ruff.toml (テンプレートの pyproject.toml が [tool.ruff] の extend で読み込む)
  • [tool.mypy] のうち規約が持つキー (disallow_untyped_defs / strict_optional など)

deptry の設定は fastship が持たない。

  • fastship-py 経由で入る依存 (injector / pydantic / pydantic-settings / sqlalchemy) も、import するならプロジェクトの dependencies に直接書く
  • deptry は uv run deptry src で実行する (. で実行すると src 直下のモジュールを依存とみなしてしまう)

プロジェクト固有の設定は pyproject.toml に書く

[tool.ruff.lint]
extend-per-file-ignores = { "src/foo/**/*.py" = ["S101"] }  # per-file-ignores と書くと規約の分が消える

[tool.mypy]
plugins = ["pydantic.mypy"]

[tool.deptry.per_rule_ignores]
DEP002 = ["uvicorn"]  # import せずに使う依存

ruff.toml / mypy.ini があると pyproject.toml の設定が読まれないため、fastship sync は失敗する。

🏛 アーキテクチャ規約 (import-linter)

fastship sync が規約を .importlinter に書き出し、root_packages を src 直下のモジュールに合わせる。 .importlinter は丸ごと fastship のもので、プロジェクト独自の contract は足せない (sync のたびに上書きされる)。 import-linter は root_packages が空だと動かないため、最初のモジュールを足すまでは書き出さない。

ヘキサゴナルアーキテクチャ (モジュール内部の依存)

core → middleware → port → application → domain → exception の向きにだけ import できる。domain を import できるのは core / application / port.adapter.persistence / port.adapter.service だけ

モジュラモノリス (モジュール間の依存)

モジュール間の import は ACL アダプター (port.adapter.service.〇〇.adapter) から相手の公開 IF (port.adapter.resource) へだけ

Metadata

Release files for fastship-py 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastship-py 0.5.0
File Size Uploaded Provenance
fastship_py-0.5.0.tar.gz 108.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for fastship-py 0.5.0
File Interpreter ABI Platform
fastship_py-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 174.6 kB

Release files / fastship_py-0.5.0.tar.gz

Download URL fastship_py-0.5.0.tar.gz
Size 108.6 kB
Tags Source
SHA-256 checksum
How to use checksums
696cb5df23c762340a6336e3e612a35629ea72fe7760e679e062f87eb1b970f5
BLAKE2b-256 checksum
How to use checksums
9b65065537f830dbce9ac4ee17c6cb3d52240ed589a00471391d23e2972ee201
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / fastship_py-0.5.0-py3-none-any.whl

Download URL fastship_py-0.5.0-py3-none-any.whl
Size 65.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ebfce60a933a692097ea923c286f29d6d79ea488bac8593ffbde2c5f8f4c02cf
BLAKE2b-256 checksum
How to use checksums
06e25a2b821a66ddc34c1f0e442deeecdf12daba331a661fab1902935c1489ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for fastship-py 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for fastship_py-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page