ttflow 0.5.8__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 (93) hide show
  1. ttflow-0.5.8/.github/workflows/ci.yml +40 -0
  2. ttflow-0.5.8/.github/workflows/docs.yml +27 -0
  3. ttflow-0.5.8/.github/workflows/python-package.yml +62 -0
  4. ttflow-0.5.8/.gitignore +147 -0
  5. ttflow-0.5.8/CLAUDE.md +72 -0
  6. ttflow-0.5.8/Makefile +35 -0
  7. ttflow-0.5.8/PKG-INFO +13 -0
  8. ttflow-0.5.8/README.md +241 -0
  9. ttflow-0.5.8/docs/api/client.md +5 -0
  10. ttflow-0.5.8/docs/api/errors.md +5 -0
  11. ttflow-0.5.8/docs/api/pause.md +5 -0
  12. ttflow-0.5.8/docs/api/powerup.md +17 -0
  13. ttflow-0.5.8/docs/api/run_context.md +5 -0
  14. ttflow-0.5.8/docs/api/state.md +10 -0
  15. ttflow-0.5.8/docs/api/state_repository.md +23 -0
  16. ttflow-0.5.8/docs/api/ttflow.md +13 -0
  17. ttflow-0.5.8/docs/api/workflow.md +11 -0
  18. ttflow-0.5.8/docs/events.md +168 -0
  19. ttflow-0.5.8/docs/guide/events.md +168 -0
  20. ttflow-0.5.8/docs/guide/state_repository.md +133 -0
  21. ttflow-0.5.8/docs/index.md +47 -0
  22. ttflow-0.5.8/docs/state_repository.md +133 -0
  23. ttflow-0.5.8/docs//351/226/213/347/231/272/343/203/241/343/203/242/00_/343/203/257/343/203/274/343/202/257/343/203/225/343/203/255/343/203/274/343/202/250/343/203/263/343/202/270/343/203/263/351/201/270/345/256/232.md +538 -0
  24. ttflow-0.5.8/docs//351/226/213/347/231/272/343/203/241/343/203/242/01_/351/226/213/347/231/272/345/206/215/351/226/213.md +49 -0
  25. ttflow-0.5.8/examples/cli.py +22 -0
  26. ttflow-0.5.8/examples/home_security.py +42 -0
  27. ttflow-0.5.8/examples/infinite_loop.py +26 -0
  28. ttflow-0.5.8/examples/infinite_loop_with_sideeffect.py +36 -0
  29. ttflow-0.5.8/examples/sample.py +89 -0
  30. ttflow-0.5.8/examples/watch_price.py +47 -0
  31. ttflow-0.5.8/mkdocs.yml +66 -0
  32. ttflow-0.5.8/pyproject.toml +59 -0
  33. ttflow-0.5.8/tests/__init__.py +0 -0
  34. ttflow-0.5.8/tests/conftest.py +8 -0
  35. ttflow-0.5.8/tests/state_repository/test_s3_state_repository.py +97 -0
  36. ttflow-0.5.8/tests/state_repository/test_state_repository.py +29 -0
  37. ttflow-0.5.8/tests/test_async_workflow.py +315 -0
  38. ttflow-0.5.8/tests/test_completed_lod.py +25 -0
  39. ttflow-0.5.8/tests/test_dynamodb_state.py +148 -0
  40. ttflow-0.5.8/tests/test_error_handling.py +20 -0
  41. ttflow-0.5.8/tests/test_event.py +98 -0
  42. ttflow-0.5.8/tests/test_every.py +23 -0
  43. ttflow-0.5.8/tests/test_load.py +56 -0
  44. ttflow-0.5.8/tests/test_load_workflow_hash.py +57 -0
  45. ttflow-0.5.8/tests/test_new_completed_and_logs.py +122 -0
  46. ttflow-0.5.8/tests/test_new_lock.py +198 -0
  47. ttflow-0.5.8/tests/test_new_pause.py +232 -0
  48. ttflow-0.5.8/tests/test_new_setup.py +173 -0
  49. ttflow-0.5.8/tests/test_new_sideeffect.py +135 -0
  50. ttflow-0.5.8/tests/test_new_state.py +195 -0
  51. ttflow-0.5.8/tests/test_new_workflow.py +274 -0
  52. ttflow-0.5.8/tests/test_notations.py +44 -0
  53. ttflow-0.5.8/tests/test_pause.py +198 -0
  54. ttflow-0.5.8/tests/test_skip.py +203 -0
  55. ttflow-0.5.8/tests/test_usecase__temperature_monitoring.py +171 -0
  56. ttflow-0.5.8/tests/test_usecase__watch_price.py +65 -0
  57. ttflow-0.5.8/tests/test_web.py +157 -0
  58. ttflow-0.5.8/tests/utils.py +10 -0
  59. ttflow-0.5.8/ttflow/__init__.py +15 -0
  60. ttflow-0.5.8/ttflow/constants.py +8 -0
  61. ttflow-0.5.8/ttflow/core/__init__.py +1 -0
  62. ttflow-0.5.8/ttflow/core/context.py +28 -0
  63. ttflow-0.5.8/ttflow/core/event.py +71 -0
  64. ttflow-0.5.8/ttflow/core/global_env.py +44 -0
  65. ttflow-0.5.8/ttflow/core/pause.py +46 -0
  66. ttflow-0.5.8/ttflow/core/run_context.py +48 -0
  67. ttflow-0.5.8/ttflow/core/state.py +66 -0
  68. ttflow-0.5.8/ttflow/core/system_events/every.py +29 -0
  69. ttflow-0.5.8/ttflow/core/system_events/pause.py +51 -0
  70. ttflow-0.5.8/ttflow/core/trigger.py +9 -0
  71. ttflow-0.5.8/ttflow/core/workflow.py +183 -0
  72. ttflow-0.5.8/ttflow/errors.py +22 -0
  73. ttflow-0.5.8/ttflow/powerup/__init__.py +0 -0
  74. ttflow-0.5.8/ttflow/powerup/run_by_cli.py +43 -0
  75. ttflow-0.5.8/ttflow/powerup/run_by_lambda.py +30 -0
  76. ttflow-0.5.8/ttflow/state_repository/__init__.py +0 -0
  77. ttflow-0.5.8/ttflow/state_repository/base.py +36 -0
  78. ttflow-0.5.8/ttflow/state_repository/buffer_cache_proxy.py +62 -0
  79. ttflow-0.5.8/ttflow/state_repository/dynamodb.py +91 -0
  80. ttflow-0.5.8/ttflow/state_repository/local_file_state.py +40 -0
  81. ttflow-0.5.8/ttflow/state_repository/on_memory_state.py +28 -0
  82. ttflow-0.5.8/ttflow/state_repository/s3.py +52 -0
  83. ttflow-0.5.8/ttflow/system_states/README.md +6 -0
  84. ttflow-0.5.8/ttflow/system_states/completed.py +64 -0
  85. ttflow-0.5.8/ttflow/system_states/event_log.py +32 -0
  86. ttflow-0.5.8/ttflow/system_states/logs.py +32 -0
  87. ttflow-0.5.8/ttflow/system_states/run_state.py +93 -0
  88. ttflow-0.5.8/ttflow/ttflow.py +237 -0
  89. ttflow-0.5.8/ttflow/utils.py +11 -0
  90. ttflow-0.5.8/ttflow/web/__init__.py +3 -0
  91. ttflow-0.5.8/ttflow/web/app.py +129 -0
  92. ttflow-0.5.8/ttflow/web/static/index.html +450 -0
  93. ttflow-0.5.8/uv.lock +1897 -0
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ name: lint
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v5
14
+ - uses: astral-sh/setup-uv@v7
15
+ - uses: actions/setup-python@v6
16
+ with:
17
+ python-version: "3.12"
18
+ - run: uv sync --extra web
19
+ - run: make lint
20
+
21
+ test:
22
+ name: test (Python ${{ matrix.python-version }})
23
+ runs-on: ubuntu-latest
24
+ strategy:
25
+ matrix:
26
+ python-version: ["3.11", "3.12", "3.13"]
27
+ steps:
28
+ - uses: actions/checkout@v5
29
+ - uses: astral-sh/setup-uv@v7
30
+ - uses: actions/setup-python@v6
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ - run: uv sync --extra web
34
+ - run: uv run pytest --cov --cov-report=xml
35
+ - name: Upload coverage to Codecov
36
+ if: matrix.python-version == '3.12'
37
+ uses: codecov/codecov-action@v5
38
+ with:
39
+ token: ${{ secrets.CODECOV_TOKEN }}
40
+ files: coverage.xml
@@ -0,0 +1,27 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ deploy:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v4
19
+
20
+ - name: Set up Python
21
+ run: uv python install
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --group docs
25
+
26
+ - name: Build and deploy docs
27
+ run: uv run --group docs mkdocs gh-deploy --force
@@ -0,0 +1,62 @@
1
+ name: build package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v5
16
+ - uses: astral-sh/setup-uv@v7
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: uv sync --extra web
21
+ - run: uv run pytest
22
+
23
+ build:
24
+ needs: test
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v5
28
+ - uses: astral-sh/setup-uv@v7
29
+ - uses: actions/setup-python@v6
30
+ with:
31
+ python-version: "3.12"
32
+ - run: uv build
33
+ - uses: actions/upload-artifact@v4
34
+ with:
35
+ name: dist
36
+ path: dist/
37
+
38
+ github-release:
39
+ needs: build
40
+ runs-on: ubuntu-latest
41
+ permissions:
42
+ contents: write
43
+ steps:
44
+ - uses: actions/download-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+ - uses: softprops/action-gh-release@v3
49
+ with:
50
+ files: dist/*
51
+
52
+ pypi-publish:
53
+ needs: build
54
+ runs-on: ubuntu-latest
55
+ permissions:
56
+ id-token: write # Trusted Publisher認証に必要
57
+ steps:
58
+ - uses: actions/download-artifact@v4
59
+ with:
60
+ name: dist
61
+ path: dist/
62
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,147 @@
1
+ ### https://raw.github.com/github/gitignore/218a941be92679ce67d0484547e3e142b2f5f6f0/Python.gitignore
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py,cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100
+ __pypackages__/
101
+
102
+ # Celery stuff
103
+ celerybeat-schedule
104
+ celerybeat.pid
105
+
106
+ # SageMath parsed files
107
+ *.sage.py
108
+
109
+ # Environments
110
+ .env
111
+ .venv
112
+ env/
113
+ venv/
114
+ ENV/
115
+ env.bak/
116
+ venv.bak/
117
+
118
+ # Spyder project settings
119
+ .spyderproject
120
+ .spyproject
121
+
122
+ # Rope project settings
123
+ .ropeproject
124
+
125
+ # mkdocs documentation
126
+ /site
127
+
128
+ # mypy
129
+ .mypy_cache/
130
+ .dmypy.json
131
+ dmypy.json
132
+
133
+ # Pyre type checker
134
+ .pyre/
135
+
136
+ # pytype static type analyzer
137
+ .pytype/
138
+
139
+ # Cython debug symbols
140
+ cython_debug/
141
+
142
+
143
+
144
+ states/*
145
+
146
+ # Claude Code
147
+ .claude/settings.local.json
ttflow-0.5.8/CLAUDE.md ADDED
@@ -0,0 +1,72 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## プロジェクト概要
6
+
7
+ ttflowは軽量なPythonワークフローエンジン。イベント駆動でワークフローを実行し、pause/resumeによる長時間ワークフローをサポートする。状態はStateRepositoryに永続化され、ワークフロー関数自体はステートレスかつ再実行可能。
8
+
9
+ ## 開発コマンド
10
+
11
+ ```bash
12
+ make test # pytest実行
13
+ make fmt # ruffでフォーマット+自動修正
14
+ make lint # ruffでリント+フォーマットチェック
15
+
16
+ # 単一テスト実行
17
+ uv run pytest tests/test_pause.py -v
18
+ uv run pytest tests/test_pause.py::test_pause_once -v
19
+
20
+ # ネットワークテスト(通常スキップ)を含める場合
21
+ uv run pytest -m network
22
+ ```
23
+
24
+ ## アーキテクチャ
25
+
26
+ ### 実行モデル
27
+
28
+ `client.run(trigger_name)` が呼ばれると:
29
+ 1. StateRepositoryのロックを取得
30
+ 2. バッファリングモード開始(状態読み書きをキャッシュ)
31
+ 3. イベントキューにトリガーイベントを追加し、キューが空になるまで処理
32
+ 4. バッファをflushしてロック解放
33
+
34
+ ### コア構造
35
+
36
+ - **`ttflow/ttflow.py`** — `Client`クラスと`setup()`。エントリポイント
37
+ - **`ttflow/core/workflow.py`** — `@workflow`/`@sideeffect`デコレータ、`exec_workflow()`
38
+ - **`ttflow/core/pause.py`** — `PauseException`によるpause/resume機構
39
+ - **`ttflow/core/state.py`** — `get_state`/`set_state`(再実行時はキャッシュから返す)
40
+ - **`ttflow/core/event.py`** — イベントキューイングと永続化
41
+ - **`ttflow/core/run_context.py`** — ワークフロー内で使うAPI(`c.get_state`, `c.log`, `c.pause_once`等)
42
+ - **`ttflow/state_repository/`** — 永続化バックエンド(local file, S3, DynamoDB, on-memory)
43
+ - **`ttflow/state_repository/buffer_cache_proxy.py`** — 読み取りキャッシュ+書き込みバッファのプロキシ層
44
+
45
+ ### 冪等性の仕組み
46
+
47
+ ワークフローはpause/resume時に最初から再実行される。安全に再実行するため:
48
+ - `set_state()` — 初回のみ実行、再実行時はno-op
49
+ - `get_state()` — 初回でキャッシュ、再実行時はキャッシュ値を返す
50
+ - `@sideeffect()` — 副作用関数を`run_id`ごとに1回だけ実行
51
+
52
+ ### トリガー種別
53
+
54
+ - `event_trigger(name)` — 名前付きイベントで発火
55
+ - `every_trigger()` — 毎回の`client.run()`で発火
56
+ - `state_trigger(state_name)` — 状態変更時に発火
57
+
58
+ ## コーディング規約
59
+
60
+ - コメント・ドキュメントは日本語
61
+ - コミットメッセージは日本語
62
+ - パッケージ管理: uv
63
+ - フォーマッタ/リンタ: ruff
64
+ - テストには`@pytest.mark.network`マーカーでネットワーク依存テストを分離
65
+ - **コード変更後は必ず `make fmt` と `make lint` を実行し、エラーがない状態にすること**(CIで同じチェックが走る)
66
+
67
+ ## ドキュメント運用
68
+
69
+ - **リファクタリング計画**: `docs/refactor_and_modernize.md` に全体計画を記載
70
+ - **開発メモ**: `docs/開発メモ/` に議事録を連番で蓄積する(例: `01_開発再開.md`, `02_xxx.md`)
71
+ - 議論と方針決定があるたびに新しいファイルを作成する
72
+ - 決定事項・経緯・背景を記録する
ttflow-0.5.8/Makefile ADDED
@@ -0,0 +1,35 @@
1
+ # Run all checks (autofix + lint + test) — same as CI
2
+ .PHONY: all
3
+ all: fmt lint test
4
+
5
+ # Run tests with verbose output
6
+ .PHONY: test
7
+ test:
8
+ uv run pytest -vv
9
+
10
+ # Run tests with coverage report
11
+ .PHONY: coverage
12
+ coverage:
13
+ uv run pytest --cov --cov-report=term-missing --cov-report=xml
14
+
15
+ # Auto-format code and apply safe lint fixes
16
+ .PHONY: fmt
17
+ fmt:
18
+ uv run ruff format .
19
+ uv run ruff check --fix .
20
+
21
+ # Check for lint errors and formatting issues (no modifications)
22
+ .PHONY: lint
23
+ lint:
24
+ uv run ruff check .
25
+ uv run ruff format --check .
26
+
27
+ # Serve docs locally with live reload
28
+ .PHONY: docs
29
+ docs:
30
+ uv run --group docs mkdocs serve
31
+
32
+ # Build docs for deployment
33
+ .PHONY: docs-build
34
+ docs-build:
35
+ uv run --group docs mkdocs build --strict
ttflow-0.5.8/PKG-INFO ADDED
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: ttflow
3
+ Version: 0.5.8
4
+ Summary: simple workflow engine
5
+ Author-email: moajo <moajodev@gmail.com>
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: boto3-stubs[essential]>=1.26.42
8
+ Requires-Dist: boto3>=1.26.42
9
+ Requires-Dist: dacite>=1.7.0
10
+ Requires-Dist: fire>=0.5.0
11
+ Provides-Extra: web
12
+ Requires-Dist: fastapi>=0.100.0; extra == 'web'
13
+ Requires-Dist: uvicorn[standard]>=0.20.0; extra == 'web'
ttflow-0.5.8/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # ttflow
2
+
3
+ [![codecov](https://codecov.io/gh/moajo/ttflow/graph/badge.svg)](https://codecov.io/gh/moajo/ttflow)
4
+
5
+ 軽量なPythonワークフローエンジンです。
6
+
7
+ ペライチのPythonスクリプトがそのままワークフローエンジンとして動きます。
8
+ サーバープロセスは不要なので、待機コストが0になります。
9
+
10
+ ## 特徴
11
+
12
+ - **サーバー不要** — 常駐プロセスなし。スクリプトを実行するだけ
13
+ - **中断と再開** — 数日〜数週間にわたるワークフローを、途中で中断して後から再開できる
14
+ - **冪等な再実行** — 再開時は最初から再実行されるが、副作用は1回しか実行されない
15
+ - **イベント駆動** — ワークフロー間をイベントで疎結合に連携
16
+ - **永続化バックエンドを選択可能** — ローカルファイル、S3、DynamoDB、オンメモリ
17
+
18
+ ## クイックスタート
19
+
20
+ ### インストール
21
+
22
+ ```bash
23
+ pip install ttflow
24
+ ```
25
+
26
+ ### 最小限のワークフロー
27
+
28
+ ```python
29
+ from ttflow import RunContext, setup
30
+ from ttflow.powerup.run_by_cli import run_by_cli
31
+
32
+ ttflow = setup(state_repository="local:state.json")
33
+
34
+ @ttflow.workflow()
35
+ def hello(c: RunContext, args: dict):
36
+ c.log("Hello, ttflow!")
37
+
38
+ run_by_cli(ttflow)
39
+ ```
40
+
41
+ ```bash
42
+ python hello.py run hello
43
+ ```
44
+
45
+ ```
46
+ Hello, ttflow!
47
+
48
+ ---------RUN SUMMARY---------
49
+ 1件のワークフローが実行されました
50
+ 1件目
51
+ ワークフロー名: hello
52
+ run_id: ...
53
+ 状態: succeeded
54
+ ログ:
55
+ - Hello, ttflow!
56
+ ```
57
+
58
+ ## コアコンセプト
59
+
60
+ ### ワークフローとトリガー
61
+
62
+ `@workflow()` でワークフローを定義し、`client.run(トリガー名)` で実行します。
63
+
64
+ ```python
65
+ @ttflow.workflow()
66
+ def 処理1(c: RunContext, args: dict):
67
+ c.log("処理1を実行します")
68
+ c.event("event1", 42) # イベントを発火
69
+
70
+ @ttflow.workflow(trigger=event_trigger("event1"))
71
+ def 処理2(c: RunContext, args: int):
72
+ c.log(f"処理2も実行します: {args}")
73
+ ```
74
+
75
+ `処理1` を実行すると `event1` が発火し、次回の `run()` で `処理2` が自動的にトリガーされます。
76
+
77
+ トリガーを省略すると、関数名がそのままトリガー名になります(`client.run("処理1")` で実行可能)。
78
+
79
+ ### 中断と再開
80
+
81
+ `c.pause_once()` でワークフローを中断し、次回の `run()` で続きから再開できます。
82
+
83
+ ```python
84
+ @ttflow.workflow()
85
+ def loop(c: RunContext, args: dict):
86
+ count = 0
87
+ while True:
88
+ c.log(f"loop: {count}週目開始")
89
+ c.pause_once() # ここで中断。次回のrun()で再開
90
+ c.log(f"loop: {count}週目おわり")
91
+ count += 1
92
+ if count > 10:
93
+ break
94
+ ```
95
+
96
+ `c.wait_event(event_name)` を使えば、特定のイベントが発行されるまで中断することもできます。承認フローなどに便利です。
97
+
98
+ ```python
99
+ @ttflow.sideeffect()
100
+ def 承認待ち(c: RunContext):
101
+ c.wait_event(f"承認:{c.get_context_data().run_id}")
102
+ ```
103
+
104
+ ### 冪等な再実行の仕組み
105
+
106
+ 中断から再開するとき、ワークフローは**関数の最初から再実行**されます。メモリ上の状態を保存しているわけではありません。
107
+
108
+ このままでは副作用が重複して実行されてしまいますが、ttflowは以下の仕組みで冪等性を保証します。
109
+
110
+ #### `@sideeffect()` — 副作用を1回だけ実行
111
+
112
+ ```python
113
+ @ttflow.sideeffect()
114
+ def send_notification(c: RunContext, message: str):
115
+ # 外部APIへの通知など。再実行時はスキップされる
116
+ requests.post("https://...", json={"message": message})
117
+ ```
118
+
119
+ `@sideeffect()` で修飾された関数は、同じ `run_id` で2回目以降の実行時には自動的にスキップされます。
120
+
121
+ #### `get_state()` / `set_state()` — 再実行に安全な状態管理
122
+
123
+ ```python
124
+ @ttflow.workflow()
125
+ def wf(c: RunContext, args: dict):
126
+ c.set_state("count", 1) # 初回のみ書き込み。再実行時はno-op
127
+ value = c.get_state("count") # 初回の値がキャッシュされ、再実行時も同じ値を返す
128
+ ```
129
+
130
+ - `set_state()` — 初回のみ実行され、再実行時には何もしない
131
+ - `get_state()` — 初回アクセス時の値をキャッシュし、再実行時も同じ値を返す
132
+
133
+ これらの仕組みにより、ワークフローは何度再実行されても、あたかも中断地点から再開したかのように振る舞います。
134
+
135
+ ## ユースケース例
136
+
137
+ ### 値段監視
138
+
139
+ 商品の値段を定期的にチェックし、希望価格以下になったら通知するワークフローです。
140
+
141
+ ```python
142
+ @ttflow.workflow()
143
+ def 買いたいもの追加(c: RunContext, args: dict):
144
+ item_name = args["item_name"]
145
+ price = args["price"]
146
+
147
+ count = 1
148
+ while True:
149
+ current_price = 値段を取得(c, item_name)
150
+ if current_price <= price:
151
+ c.log(f"{count}回目: {item_name}は{current_price}円!")
152
+ return
153
+ c.log(f"{count}回目: {item_name}は{current_price}で買えませんでした")
154
+ count += 1
155
+ c.pause_once() # 次回のrun()まで待機
156
+
157
+ @ttflow.sideeffect()
158
+ def 値段を取得(c: RunContext, item_name: str) -> int:
159
+ # 実際にはここで外部APIから価格を取得する
160
+ return fetch_price_from_api(item_name)
161
+ ```
162
+
163
+ ```bash
164
+ python watch_price.py run 買いたいもの追加 '{"item_name":"レモン","price":150}'
165
+ python watch_price.py run # 定期実行(cronなどで)
166
+ ```
167
+
168
+ `pause_once()` のたびに中断されるので、cronやCloudWatchスケジューラで定期的に `run` を呼べば、値段が下がるまで繰り返しチェックできます。複数の商品を同時に監視することもできます。
169
+
170
+ ### 承認フロー
171
+
172
+ 外部からのイベントを待って再開するパターンです。
173
+
174
+ ```python
175
+ @ttflow.workflow()
176
+ def デプロイ(c: RunContext, args: dict):
177
+ notification(c, "デプロイを開始します")
178
+ 承認待ち(c)
179
+ # ↑ 「承認」イベントが来るまで中断される
180
+ notification(c, "承認されました。デプロイを続行します")
181
+
182
+ @ttflow.sideeffect()
183
+ def 承認待ち(c: RunContext):
184
+ c.wait_event(f"承認:{c.get_context_data().run_id}")
185
+
186
+ @ttflow.sideeffect()
187
+ def notification(c: RunContext, message: str):
188
+ c.log(message)
189
+ ```
190
+
191
+ ```bash
192
+ python deploy.py run デプロイ # 中断される
193
+ python deploy.py run 承認:<run_id> # 承認イベントを発行 → 再開
194
+ ```
195
+
196
+ ## StateRepository(永続化バックエンド)
197
+
198
+ `setup()` の `state_repository` 引数でバックエンドを選択します。
199
+
200
+ | 指定 | 用途 |
201
+ | ------------------------- | ------------------------------------ |
202
+ | `"local:state.json"` | ローカルファイル。開発・個人利用向け |
203
+ | `"s3:bucket-name/prefix"` | S3。Lambda等のサーバーレス環境向け |
204
+ | `"dynamodb:table-name"` | DynamoDB |
205
+ | `"onmemory"` | オンメモリ。テスト向け |
206
+
207
+ ```python
208
+ ttflow = setup(state_repository="s3:my-bucket/workflows")
209
+ ```
210
+
211
+ ## 運用方法
212
+
213
+ ワークフローは実行してすぐ終了するただのスクリプトなので、様々な運用方法が考えられます。
214
+
215
+ ### ローカル実行
216
+
217
+ ```bash
218
+ python my_workflow.py run トリガー名
219
+ python my_workflow.py run # 中断中のワークフローを再開
220
+ ```
221
+
222
+ `run_by_cli()` を使うと、[python-fire](https://github.com/google/python-fire) ベースのCLIが利用できます。
223
+
224
+ ### AWS Lambda
225
+
226
+ Lambda上で動かすのが最も簡単な運用方法です。
227
+
228
+ ```python
229
+ from ttflow import setup
230
+ from ttflow.powerup.run_by_lambda import run_by_lambda
231
+
232
+ ttflow = setup(state_repository="s3:my-bucket/state")
233
+
234
+ # ワークフロー定義...
235
+
236
+ handler = run_by_lambda(ttflow)
237
+ ```
238
+
239
+ - Lambda関数URLやAPI Gatewayからトリガー名を指定して実行
240
+ - CloudWatchスケジューラで定期的に `run()` を呼んで中断中のワークフローを再開
241
+ - 永続化バックエンドにはS3やDynamoDBを使用
@@ -0,0 +1,5 @@
1
+ # Client
2
+
3
+ ワークフローエンジンのメインクラスです。
4
+
5
+ ::: ttflow.ttflow.Client
@@ -0,0 +1,5 @@
1
+ # エラー
2
+
3
+ ttflowが定義する例外クラスです。
4
+
5
+ ::: ttflow.errors
@@ -0,0 +1,5 @@
1
+ # Pause/Resume
2
+
3
+ ワークフローの中断・再開機構です。
4
+
5
+ ::: ttflow.core.pause
@@ -0,0 +1,17 @@
1
+ # Powerup
2
+
3
+ CLI・Lambda統合のユーティリティです。
4
+
5
+ ## CLI
6
+
7
+ ::: ttflow.powerup.run_by_cli
8
+ options:
9
+ members:
10
+ - run_by_cli
11
+
12
+ ## Lambda
13
+
14
+ ::: ttflow.powerup.run_by_lambda
15
+ options:
16
+ members:
17
+ - run_by_lambda
@@ -0,0 +1,5 @@
1
+ # RunContext
2
+
3
+ ワークフロー関数内で使用するランタイムAPIです。
4
+
5
+ ::: ttflow.core.run_context.RunContext
@@ -0,0 +1,10 @@
1
+ # 状態管理
2
+
3
+ ワークフローの状態を管理するAPIです。
4
+
5
+ ::: ttflow.core.state
6
+ options:
7
+ members:
8
+ - set_state
9
+ - get_state
10
+ - add_list_state