fastship-py 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. fastship_py-0.4.0/.github/workflows/ci.yml +50 -0
  2. fastship_py-0.4.0/.github/workflows/claude.yml +56 -0
  3. fastship_py-0.4.0/.github/workflows/publish.yml +75 -0
  4. fastship_py-0.4.0/.gitignore +133 -0
  5. fastship_py-0.4.0/CONTRIBUTING.md +69 -0
  6. fastship_py-0.4.0/LICENSE +21 -0
  7. fastship_py-0.4.0/PKG-INFO +176 -0
  8. fastship_py-0.4.0/README.md +155 -0
  9. fastship_py-0.4.0/Taskfile.yml +32 -0
  10. fastship_py-0.4.0/lefthook.yml +20 -0
  11. fastship_py-0.4.0/mise.toml +4 -0
  12. fastship_py-0.4.0/pyproject.toml +171 -0
  13. fastship_py-0.4.0/src/fastship/__init__.py +13 -0
  14. fastship_py-0.4.0/src/fastship/common/__init__.py +5 -0
  15. fastship_py-0.4.0/src/fastship/common/application/__init__.py +9 -0
  16. fastship_py-0.4.0/src/fastship/common/application/application_service_life_cycle.py +121 -0
  17. fastship_py-0.4.0/src/fastship/common/application/unit_of_work.py +53 -0
  18. fastship_py-0.4.0/src/fastship/common/domain/__init__.py +0 -0
  19. fastship_py-0.4.0/src/fastship/common/domain/model/__init__.py +11 -0
  20. fastship_py-0.4.0/src/fastship/common/domain/model/domain_event.py +90 -0
  21. fastship_py-0.4.0/src/fastship/common/domain/model/domain_registry.py +22 -0
  22. fastship_py-0.4.0/src/fastship/common/domain/model/event_context.py +27 -0
  23. fastship_py-0.4.0/src/fastship/common/event/__init__.py +5 -0
  24. fastship_py-0.4.0/src/fastship/common/event/event_context_provider.py +56 -0
  25. fastship_py-0.4.0/src/fastship/common/event/event_store.py +24 -0
  26. fastship_py-0.4.0/src/fastship/common/event/stored_event.py +73 -0
  27. fastship_py-0.4.0/src/fastship/common/exception/__init__.py +4 -0
  28. fastship_py-0.4.0/src/fastship/common/exception/error_code.py +50 -0
  29. fastship_py-0.4.0/src/fastship/common/exception/system_exception.py +16 -0
  30. fastship_py-0.4.0/src/fastship/common/module.py +82 -0
  31. fastship_py-0.4.0/src/fastship/common/notification/__init__.py +20 -0
  32. fastship_py-0.4.0/src/fastship/common/notification/consumed_notification.py +31 -0
  33. fastship_py-0.4.0/src/fastship/common/notification/consumed_notification_store.py +36 -0
  34. fastship_py-0.4.0/src/fastship/common/notification/notification.py +72 -0
  35. fastship_py-0.4.0/src/fastship/common/notification/notification_publisher.py +9 -0
  36. fastship_py-0.4.0/src/fastship/common/notification/notification_reader.py +33 -0
  37. fastship_py-0.4.0/src/fastship/common/notification/notification_serializer.py +17 -0
  38. fastship_py-0.4.0/src/fastship/common/notification/published_notification_tracker.py +60 -0
  39. fastship_py-0.4.0/src/fastship/common/notification/published_notification_tracker_store.py +17 -0
  40. fastship_py-0.4.0/src/fastship/common/port/__init__.py +0 -0
  41. fastship_py-0.4.0/src/fastship/common/port/adapter/__init__.py +0 -0
  42. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/__init__.py +5 -0
  43. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/exchange_listener.py +19 -0
  44. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/message_publisher.py +13 -0
  45. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/message_subscriber.py +117 -0
  46. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/stub/__init__.py +3 -0
  47. fastship_py-0.4.0/src/fastship/common/port/adapter/messaging/stub/message_publisher_stub.py +19 -0
  48. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/__init__.py +0 -0
  49. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/inmem/__init__.py +11 -0
  50. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/inmem/in_mem_consumed_notification_store.py +27 -0
  51. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/inmem/in_mem_event_store.py +31 -0
  52. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/inmem/in_mem_published_notification_tracker_store.py +20 -0
  53. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/inmem/in_mem_unit_of_work.py +37 -0
  54. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/__init__.py +15 -0
  55. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/database.py +27 -0
  56. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/database_settings.py +19 -0
  57. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/routing_session.py +165 -0
  58. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/session_preparer.py +33 -0
  59. fastship_py-0.4.0/src/fastship/common/port/adapter/persistence/sqlalchemy/sqlalchemy_unit_of_work.py +161 -0
  60. fastship_py-0.4.0/src/fastship/common/settings.py +34 -0
  61. fastship_py-0.4.0/src/fastship/common/testing/__init__.py +13 -0
  62. fastship_py-0.4.0/src/fastship/common/testing/contracts.py +111 -0
  63. fastship_py-0.4.0/src/fastship/di/__init__.py +6 -0
  64. fastship_py-0.4.0/src/fastship/di/container.py +53 -0
  65. fastship_py-0.4.0/src/fastship/di/di.py +76 -0
  66. fastship_py-0.4.0/src/fastship/linter/__init__.py +0 -0
  67. fastship_py-0.4.0/src/fastship/linter/architecture.py +145 -0
  68. fastship_py-0.4.0/src/fastship/migration/__init__.py +15 -0
  69. fastship_py-0.4.0/src/fastship/migration/metadata.py +22 -0
  70. fastship_py-0.4.0/test/__init__.py +0 -0
  71. fastship_py-0.4.0/test/test_/343/203/206/343/203/274/343/203/226/343/203/253/343/201/256/345/237/272/345/272/225.py +113 -0
  72. fastship_py-0.4.0/test/test_/343/203/210/343/203/251/343/203/263/343/202/266/343/202/257/343/202/267/343/203/247/343/203/263/345/242/203/347/225/214.py +147 -0
  73. fastship_py-0.4.0/test/test_/343/203/235/343/203/274/343/203/210/351/201/251/345/220/210/343/203/206/343/202/271/343/203/210.py +47 -0
  74. fastship_py-0.4.0/test/test_/343/203/236/343/202/244/343/202/260/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263.py +114 -0
  75. fastship_py-0.4.0/test/test_/345/256/237/350/241/214/346/226/207/350/204/210/343/201/256/345/210/273/345/215/260.py +99 -0
  76. fastship_py-0.4.0/test/test_/350/252/255/343/201/277/346/233/270/343/201/215/343/201/256/346/214/257/343/202/212/345/210/206/343/201/221.py +500 -0
  77. fastship_py-0.4.0/uv.lock +742 -0
@@ -0,0 +1,50 @@
1
+ name: Python CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ workflow_dispatch:
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ validation:
14
+ name: 検査
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 10
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ persist-credentials: false
23
+
24
+ - name: uv のセットアップ
25
+ # setup-uv は major/minor タグを publish していないため commit hash でピンする
26
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
27
+ with:
28
+ python-version: "3.14"
29
+ enable-cache: true
30
+
31
+ - name: 依存の同期
32
+ # --locked で uv.lock と pyproject.toml のズレも検出する。
33
+ # --all-extras は Taskfile の init と同じ経路 (mypy が sqlalchemy アダプタも検査する)。
34
+ run: uv sync --locked --all-extras
35
+
36
+ - name: フォーマット検査
37
+ # 整形は ruff format が唯一の基準。差分があれば `task style:fix` で解消する。
38
+ run: uv run ruff format --check .
39
+
40
+ - name: Ruff リント
41
+ run: uv run ruff check .
42
+
43
+ - name: 型チェック
44
+ run: uv run mypy
45
+
46
+ - name: テスト
47
+ run: uv run pytest
48
+
49
+ - name: Import Linter
50
+ run: uv run lint-imports --no-logo
@@ -0,0 +1,56 @@
1
+ name: "🧠 Claude"
2
+
3
+ # 組織共通の Claude ワークフロー (theindiehacker/devops) を呼び出す caller
4
+ on:
5
+ issue_comment:
6
+ types: [created]
7
+ pull_request_review_comment:
8
+ types: [created]
9
+ pull_request_review:
10
+ types: [submitted]
11
+ issues:
12
+ types: [opened]
13
+
14
+ # write は呼び出し先の job に渡す分だけを job 側で与える
15
+ permissions: {}
16
+
17
+ jobs:
18
+ code-review:
19
+ if: github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/code-review')
20
+ permissions:
21
+ contents: read
22
+ actions: read
23
+ pull-requests: write
24
+ issues: write
25
+ id-token: write
26
+ uses: theindiehacker/devops/.github/workflows/claude-code-review.yml@a6db12cef36ce69e0d76ce26c380c2255d4764ea # v1.4.1
27
+ secrets:
28
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
29
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
30
+
31
+ security-review:
32
+ # 実際の pwn request 対策 (ロール判定など) は呼び出し先 claude-security-review.yml の job if で行う。
33
+ # ここは code-review job と同様、呼び出し先を起動するかどうかの粗いフィルタ
34
+ if: github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/security-review')
35
+ permissions:
36
+ contents: read
37
+ actions: read
38
+ pull-requests: write
39
+ issues: write
40
+ id-token: write
41
+ uses: theindiehacker/devops/.github/workflows/claude-security-review.yml@a6db12cef36ce69e0d76ce26c380c2255d4764ea # v1.4.1
42
+ secrets:
43
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
44
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
45
+
46
+ claude:
47
+ permissions:
48
+ contents: write
49
+ actions: read
50
+ pull-requests: write
51
+ issues: write
52
+ id-token: write
53
+ uses: theindiehacker/devops/.github/workflows/claude.yml@a6db12cef36ce69e0d76ce26c380c2255d4764ea # v1.4.1
54
+ secrets:
55
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
56
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -0,0 +1,75 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ # 既定は無権限。必要な job にだけ最小権限を与える。
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: publish-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ build:
16
+ name: 配布物のビルド
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 10
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: uv のセットアップ
27
+ # setup-uv は major/minor タグを publish していないため commit hash でピンする
28
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
29
+ with:
30
+ python-version: "3.14"
31
+ # リリース成果物のビルドではキャッシュを使わない。
32
+ # PR 経由で汚染されたキャッシュが配布物に混入する経路を断つ (zizmor: cache-poisoning)。
33
+ enable-cache: false
34
+
35
+ - name: タグと pyproject.toml の version が一致することを検証
36
+ env:
37
+ TAG: ${{ github.event.release.tag_name }}
38
+ run: |
39
+ VERSION="$(python3 -c 'import tomllib, pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
40
+ if [ "${TAG#v}" != "$VERSION" ]; then
41
+ echo "::error::タグ ${TAG} と pyproject.toml の version ${VERSION} が一致しません"
42
+ exit 1
43
+ fi
44
+
45
+ - name: sdist と wheel をビルド
46
+ run: uv build --out-dir dist
47
+
48
+ - name: 配布物を artifact として保存
49
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
50
+ with:
51
+ name: python-package-distributions
52
+ path: dist/
53
+
54
+ publish-to-pypi:
55
+ name: PyPI へ公開
56
+ needs: [ build ]
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 10
59
+ environment:
60
+ name: pypi
61
+ url: https://pypi.org/p/fastship-py
62
+ permissions:
63
+ # OIDC トークンの発行に必須。workflow 単位ではなく job 単位で付与する (公式の強い推奨)
64
+ id-token: write
65
+ steps:
66
+ - name: 配布物を取得
67
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
68
+ with:
69
+ name: python-package-distributions
70
+ path: dist/
71
+
72
+ - name: PyPI へアップロード
73
+ # Trusted Publishing (OIDC) のため認証情報の指定は一切不要。
74
+ # v1.11.0 以降、Trusted Publishing 時は PEP 740 attestation が自動生成・添付される。
75
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,133 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # JetBrains IDE (IntelliJ IDEA / PyCharm)
132
+ # 各開発者のローカル設定なので git 管理しない
133
+ .idea/
@@ -0,0 +1,69 @@
1
+ # Development
2
+
3
+ ## Set up
4
+ ```bash
5
+ task init # 依存インストール
6
+ task test # テスト
7
+ task style:check # ruff format / ruff / mypy / import-linter (自分自身への契約検査)
8
+ task style:fix # 整形 + lint の自動修正
9
+ ```
10
+
11
+ ## Debug
12
+
13
+ ローカルの変更を、実際のリポジトリに撃って確かめる手順。
14
+
15
+ ```bash
16
+ cd path/to/対象プロジェクト
17
+ uv add --dev --editable path/to/fastship-py # 一度だけ
18
+ ```
19
+
20
+ ## Release
21
+
22
+ PyPI への公開は GitHub Release をトリガーに、[Trusted Publishing (OIDC)](https://docs.pypi.org/trusted-publishers/) で自動実行される。
23
+ API トークンはリポジトリに置かない。
24
+
25
+ ```bash
26
+ # 1. pyproject.toml の version を上げて main へマージする
27
+ # 2. その version と同じタグで Release を作る (v プレフィックス付き)
28
+ gh release create v0.1.0 --generate-notes
29
+ ```
30
+
31
+ タグと `pyproject.toml` の version が食い違うとワークフローは公開前に落ちる。
32
+ `__version__` は `pyproject.toml` から読むため、バージョンを書き換える箇所は `pyproject.toml` の 1 行だけ。
33
+
34
+ ## CI
35
+
36
+ PR には以下のチェックが走る。
37
+
38
+ | ワークフロー | 検知するもの |
39
+ |:---|:---|
40
+ | `ci.yml` | 整形 (ruff format) / lint (ruff) / 型 (mypy) / テスト / アーキテクチャ規約 (import-linter) |
41
+ | `claude.yml` | 検知ではなく呼び出し用。PR に `/code-review`・`/security-review` とコメント、または `@claude` にメンションで Claude を起動する |
42
+ | org の必須ワークフロー | シークレット混入 (Gitleaks) / SAST (Semgrep) / 依存パッケージの脆弱性・IaC の設定ミス (Trivy) / GitHub Actions 定義 (zizmor / ghalint / actionlint) など |
43
+
44
+ org の必須ワークフローは [theindiehacker/devops](https://github.com/theindiehacker/devops) の定義を org ルールセットで全リポジトリに適用しているため、本リポジトリには置かない。
45
+
46
+ ### `ci.yml` を必須にする (リポジトリ管理者向け)
47
+
48
+ ワークフローを置いただけではマージはブロックされない。
49
+ Settings → Rules → Rulesets → New branch ruleset で、
50
+ **Target branches** を `Include default branch`、
51
+ **Require status checks to pass** に `検査` を登録する。
52
+
53
+ > チェック名は job 名がそのまま出たもの。
54
+ > job 名を変えると必須チェックの名前も変わり、PR が永久に pending になるため変更しない。
55
+
56
+ ### 検知の抑制
57
+
58
+ 誤検知の抑制は、org ルールセット「セキュリティ設定変更の承認必須化」の対象である
59
+ 設定ファイル (`.gitleaksignore` / `.trivyignore` / `.semgrepignore` / `.github/zizmor.yml`) に限定する。
60
+ 各ツールのインライン抑制コメント (gitleaks の allow / Semgrep の nosem /
61
+ Trivy と zizmor の ignore) は承認ゲートを迂回するため、
62
+ 各ワークフローで明示的に無効化、または存在自体を禁止している。
63
+
64
+ > ここでトークンを原文のまま書いていないのは、Trivy ワークフローの禁止チェックが
65
+ > リポジトリ全体を grep するため、この文書自身が検知されてしまうため。
66
+
67
+ これらの設定ファイルは**検出が実際に出て、誤検知だと判断できた時にだけ**作る。
68
+ 先回りして空ファイルを置かないこと。特に `.semgrepignore` は、存在するだけで
69
+ Semgrep の組み込み既定除外リストを置き換えてしまう。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 taiyo tamura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: fastship-py
3
+ Version: 0.4.0
4
+ Summary: モジュラモノリス + DDD のカーネル。集約・ドメインイベント・UnitOfWork・トランザクショナル outbox/inbox を業務語彙ゼロで提供する
5
+ Project-URL: Homepage, https://github.com/theindiehacker/fastship-py
6
+ Project-URL: Repository, https://github.com/theindiehacker/fastship-py
7
+ Author-email: taiyo tamura <gtaiyou24@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ddd,hexagonal-architecture,modular-monolith,outbox,unit-of-work
11
+ Requires-Python: >=3.14
12
+ Requires-Dist: injector<1.0.0,>=0.22.0
13
+ Requires-Dist: pydantic-settings<3.0.0,>=2.14.2
14
+ Requires-Dist: pydantic<3.0.0,>=2.13.0
15
+ Requires-Dist: pytz>=2026.2
16
+ Provides-Extra: linter
17
+ Requires-Dist: import-linter<3.0.0,>=2.14; extra == 'linter'
18
+ Provides-Extra: sqlalchemy
19
+ Requires-Dist: sqlalchemy<3.0.0,>=2.0.51; extra == 'sqlalchemy'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # 🚀 fastship-py
23
+
24
+ ```bash
25
+ uv add fastship-py
26
+ uv add "fastship-py[sqlalchemy]" # SQLAlchemy アダプタも使う場合
27
+ ```
28
+
29
+ ## 📚 How To
30
+ ### 💾 Persistence
31
+
32
+ <details><summary><b>💾 テーブルクラス ( SQLAlchemy ) の実装</b></summary>
33
+
34
+ `〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver.users_table_row.py`
35
+ ```python
36
+ from sqlalchemy import String
37
+ from sqlalchemy.orm import Mapped, mapped_column
38
+
39
+ # NOTE: テーブルクラスは `DataBase` を継承して作ること
40
+ from fastship.common.port.adapter.persistence.sqlalchemy import DataBase
41
+
42
+
43
+ class UsersTableRow(DataBase):
44
+ __tablename__ = "users"
45
+
46
+ id: Mapped[str] = mapped_column(String(36), primary_key=True)
47
+ name: Mapped[str] = mapped_column(String(50), index=True) # index 名は ix_users_name
48
+ ```
49
+
50
+ `〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver.__init__.py`
51
+ ```python
52
+ from .users_table_row import UsersTableRow
53
+ # 他のテーブルクラス
54
+ ```
55
+
56
+ テーブルクラスを足したら、そのモジュールの `tables` にドライバーモジュールのパスを足す
57
+
58
+ ```python
59
+ class 〇〇(AppModule):
60
+ @property
61
+ @override
62
+ def tables(self) -> list[str]:
63
+ return ["〇〇.port.adapter.persistence.repository.sqlalchemy.user.driver"]
64
+ ```
65
+
66
+ </details>
67
+
68
+ <details><summary><b>🚚 マイグレーション (Alembic)</b></summary>
69
+
70
+ 事前にマイグレーション用のパッケージを生成
71
+ ```bash
72
+ uv add alembic
73
+ uv run alembic init migration
74
+ ```
75
+
76
+ `migration/env.py`:
77
+ ```python
78
+ # alembic init の生成物から、target_metadata = None をこの 3 行に差し替える
79
+
80
+ from fastship.migration import load_metadata
81
+ from core import Backend # 全モジュールを束ねた CompositeModule
82
+
83
+ target_metadata = load_metadata(Backend())
84
+ ```
85
+
86
+ </details>
87
+
88
+ <details><summary><b>🔀 リードレプリカへの読み書き振り分け (SQLAlchemy)</b></summary>
89
+
90
+ 接続先はインフラが環境変数で注入する (Secret Manager の値は実行基盤で環境変数として公開する)。アプリ側では設定しない。
91
+
92
+ | 環境変数 | 内容 |
93
+ |:---|:---|
94
+ | `DATABASE_WRITER_URL` | プライマリ。必須 |
95
+ | `DATABASE_READER_URL` | リードレプリカ (Read Pool のエンドポイント)。未設定ならすべてプライマリへ送る |
96
+
97
+ ```python
98
+ from fastship.common.port.adapter.persistence.sqlalchemy import SQLAlchemyUnitOfWork
99
+
100
+ unit_of_work = SQLAlchemyUnitOfWork.from_settings(pool_pre_ping=True)
101
+ ```
102
+
103
+ トランザクション (`unit_of_work.session()`) は、読み取りも含めてすべてプライマリで実行する。
104
+ レプリカの遅延した値を元に更新すると、プライマリの新しい値を上書きしてしまう (lost update) ため。
105
+ 更新系の生 SQL (`text()`) もここで実行する。
106
+
107
+ トランザクション外の読み取り (`unit_of_work.query()`) は、以下の規則でリーダーとライターに振り分ける。
108
+
109
+ | クエリ | 送り先 |
110
+ |:---|:---|
111
+ | 読み取り (`select()` / `session.get()` / `text()`) | リーダー |
112
+ | 書き込み (flush / `insert()` / `update()` / `delete()`) | ライター |
113
+ | `SELECT ... FOR UPDATE` (`with_for_update()`) | ライター |
114
+ | 一度でもライターへ送ったセッションの以降のクエリ | ライター (書き込み直後に古いデータを読まない) |
115
+ | `session.use_writer()` を呼んだ後のクエリ | ライター (レプリカ遅延を許容できない読み取り) |
116
+
117
+ ```python
118
+ with unit_of_work.query() as session:
119
+ session.use_writer()
120
+ session.get(...)
121
+ ```
122
+
123
+ 認可の判定 (無効化された API キー、停止されたユーザー、取り消された権限など) は、レプリカ遅延の間に
124
+ 古い状態で許可しないよう、`session.use_writer()` を呼んでプライマリから読むこと。
125
+
126
+ Read Pool のノード数を増減しても、エンドポイントは変わらないためアプリ側の変更は要らない。
127
+ `SessionPreparer` (RLS の `SET LOCAL` など) は、振り分け先のコネクションそれぞれに適用される。
128
+
129
+ </details>
130
+
131
+
132
+ ### 🛠️ Other
133
+
134
+ <details><summary><b>📦 DI コンテナ</b></summary>
135
+
136
+ 登録方法:
137
+ ```python
138
+ from sqlalchemy import create_engine
139
+
140
+ from fastship.common.application import UnitOfWork
141
+ from fastship.common.port.adapter.persistence.inmem import InMemUnitOfWork
142
+ from fastship.common.port.adapter.persistence.sqlalchemy import SQLAlchemyUnitOfWork
143
+ from fastship.di import DI, DIContainer
144
+
145
+ engine = create_engine("postgresql+psycopg://...")
146
+
147
+ DIContainer.instance().register(
148
+ DI.of(UnitOfWork, {"InMem": InMemUnitOfWork()}, SQLAlchemyUnitOfWork(engine)),
149
+ )
150
+ ```
151
+
152
+ 利用方法:
153
+ ```python
154
+ from fastship.di import DI, DIContainer
155
+
156
+ # `DI_FOR_PY=InMem` なら InMemUnitOfWork のインタンスを取得
157
+ # `DI_FOR_PY=` なら SQLAlchemyUnitOfWork のインタンスを取得
158
+ unit_of_work = DIContainer.instance().resolve(UnitOfWork)
159
+ ```
160
+
161
+ ```python
162
+ from injector import inject
163
+
164
+
165
+ class 〇〇ApplicationService:
166
+ @inject
167
+ def __init__(self, 〇〇_repository: 〇〇Repository) -> None: ...
168
+ ```
169
+
170
+ ```python
171
+ from fastship.common.domain.model import DomainRegistry
172
+
173
+ 〇〇_repository = DomainRegistry.resolve(〇〇Repository)
174
+ ```
175
+
176
+ </details>