twpm 0.2.3__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.
- twpm-0.2.3/.claude/settings.local.json +11 -0
- twpm-0.2.3/.gitignore +216 -0
- twpm-0.2.3/.python-version +1 -0
- twpm-0.2.3/CLAUDE.md +140 -0
- twpm-0.2.3/LICENSE +21 -0
- twpm-0.2.3/PKG-INFO +173 -0
- twpm-0.2.3/README.md +148 -0
- twpm-0.2.3/docs/conditional-example.png +0 -0
- twpm-0.2.3/docs/core-components.png +0 -0
- twpm-0.2.3/docs/linked-list.png +0 -0
- twpm-0.2.3/examples/__init__.py +0 -0
- twpm-0.2.3/examples/cli/main.py +179 -0
- twpm-0.2.3/examples/cli/tests/__init__.py +1 -0
- twpm-0.2.3/mise.toml +2 -0
- twpm-0.2.3/pyproject.toml +74 -0
- twpm-0.2.3/tests/__init__.py +0 -0
- twpm-0.2.3/tests/core/__init__.py +0 -0
- twpm-0.2.3/tests/core/primitives/__init__.py +0 -0
- twpm-0.2.3/tests/core/primitives/test_condition.py +245 -0
- twpm-0.2.3/tests/core/primitives/test_display_message.py +43 -0
- twpm-0.2.3/tests/core/primitives/test_pool.py +102 -0
- twpm-0.2.3/tests/core/primitives/test_progress.py +65 -0
- twpm-0.2.3/tests/core/primitives/test_question.py +66 -0
- twpm-0.2.3/tests/core/primitives/test_quiz_node.py +196 -0
- twpm-0.2.3/tests/core/primitives/test_quiz_summary.py +229 -0
- twpm-0.2.3/tests/core/primitives/test_task.py +162 -0
- twpm-0.2.3/tests/core/test_chain.py +294 -0
- twpm-0.2.3/tests/core/test_cursor.py +404 -0
- twpm-0.2.3/tests/core/test_orchestrator.py +542 -0
- twpm-0.2.3/tests/dsa/__init__.py +0 -0
- twpm-0.2.3/tests/dsa/test_doublelinkedlist.py +124 -0
- twpm-0.2.3/tests/dsa/test_linkedlist.py +165 -0
- twpm-0.2.3/twpm/__init__.py +36 -0
- twpm-0.2.3/twpm/constants.py +3 -0
- twpm-0.2.3/twpm/core/__init__.py +16 -0
- twpm-0.2.3/twpm/core/base/__init__.py +28 -0
- twpm-0.2.3/twpm/core/base/enums.py +25 -0
- twpm-0.2.3/twpm/core/base/models.py +88 -0
- twpm-0.2.3/twpm/core/base/node.py +52 -0
- twpm-0.2.3/twpm/core/base/types.py +12 -0
- twpm-0.2.3/twpm/core/chain.py +291 -0
- twpm-0.2.3/twpm/core/container.py +38 -0
- twpm-0.2.3/twpm/core/cursor.py +141 -0
- twpm-0.2.3/twpm/core/decorators.py +53 -0
- twpm-0.2.3/twpm/core/depedencies.py +5 -0
- twpm-0.2.3/twpm/core/orchestrator.py +179 -0
- twpm-0.2.3/twpm/core/primitives/__init__.py +22 -0
- twpm-0.2.3/twpm/core/primitives/condition.py +39 -0
- twpm-0.2.3/twpm/core/primitives/display_message.py +45 -0
- twpm-0.2.3/twpm/core/primitives/pool.py +99 -0
- twpm-0.2.3/twpm/core/primitives/progress.py +66 -0
- twpm-0.2.3/twpm/core/primitives/question.py +64 -0
- twpm-0.2.3/twpm/core/primitives/quiz/__init__.py +4 -0
- twpm-0.2.3/twpm/core/primitives/quiz/quiz_node.py +113 -0
- twpm-0.2.3/twpm/core/primitives/quiz/quiz_summary.py +86 -0
- twpm-0.2.3/twpm/core/primitives/summary.py +57 -0
- twpm-0.2.3/twpm/core/primitives/task.py +20 -0
- twpm-0.2.3/twpm/dsa/__init__.py +0 -0
- twpm-0.2.3/twpm/dsa/doublelinkedlist.py +110 -0
- twpm-0.2.3/twpm/dsa/linkedlist.py +138 -0
- twpm-0.2.3/uv.lock +271 -0
twpm-0.2.3/.gitignore
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
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
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
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
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
|
|
204
|
+
# Ruff stuff:
|
|
205
|
+
.ruff_cache/
|
|
206
|
+
|
|
207
|
+
# PyPI configuration file
|
|
208
|
+
.pypirc
|
|
209
|
+
|
|
210
|
+
# Marimo
|
|
211
|
+
marimo/_static/
|
|
212
|
+
marimo/_lsp/
|
|
213
|
+
__marimo__/
|
|
214
|
+
|
|
215
|
+
# Streamlit
|
|
216
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.12
|
twpm-0.2.3/CLAUDE.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
twpm is a workflow builder library that simplifies creating dynamic workflows using a linked-list-based execution model. The project combines data structure implementations (for learning) with a workflow orchestration system.
|
|
8
|
+
|
|
9
|
+
## Common Development Commands
|
|
10
|
+
|
|
11
|
+
This project uses **uv** for Python package management. uv is a fast Python package installer and resolver written in Rust.
|
|
12
|
+
|
|
13
|
+
### Setup
|
|
14
|
+
```bash
|
|
15
|
+
# Install dependencies (including dev dependencies)
|
|
16
|
+
uv sync
|
|
17
|
+
|
|
18
|
+
# Install only production dependencies
|
|
19
|
+
uv sync --no-dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Testing
|
|
23
|
+
```bash
|
|
24
|
+
# Run all tests
|
|
25
|
+
uv run pytest
|
|
26
|
+
|
|
27
|
+
# Run tests with verbose output
|
|
28
|
+
uv run pytest -v
|
|
29
|
+
|
|
30
|
+
# Run specific test file
|
|
31
|
+
uv run pytest tests/core/test_orchestrator.py
|
|
32
|
+
|
|
33
|
+
# Run single test
|
|
34
|
+
uv run pytest tests/core/test_orchestrator.py::TestOrchestrator::test_single_node_execution
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Linting and Formatting
|
|
38
|
+
```bash
|
|
39
|
+
# Check code with ruff (linter)
|
|
40
|
+
uv run ruff check .
|
|
41
|
+
|
|
42
|
+
# Auto-fix linting issues
|
|
43
|
+
uv run ruff check --fix .
|
|
44
|
+
|
|
45
|
+
# Format code with ruff
|
|
46
|
+
uv run ruff format .
|
|
47
|
+
|
|
48
|
+
# Check formatting without changes
|
|
49
|
+
uv run ruff format --check .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Building
|
|
53
|
+
```bash
|
|
54
|
+
# Build package for distribution
|
|
55
|
+
uv build
|
|
56
|
+
|
|
57
|
+
# Build wheel only
|
|
58
|
+
uv build --wheel
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Architecture
|
|
62
|
+
|
|
63
|
+
### Core Workflow System (`twpm/core/`)
|
|
64
|
+
|
|
65
|
+
The workflow system is built on a linked-list architecture where nodes execute sequentially:
|
|
66
|
+
|
|
67
|
+
1. **Node** (`core/base/node.py`): Abstract base class for all workflow nodes
|
|
68
|
+
- Nodes form a singly-linked list via `next` and `previous` references
|
|
69
|
+
- Each node implements `async execute(data: ListData) -> NodeResult`
|
|
70
|
+
- Node execution is wrapped with `@safe_execute()` decorator to guarantee NodeResult return
|
|
71
|
+
|
|
72
|
+
2. **Orchestrator** (`core/orchestrator.py`): Main workflow execution engine
|
|
73
|
+
- Manages node execution flow sequentially
|
|
74
|
+
- Maintains shared `ListData` for passing information between nodes
|
|
75
|
+
- Handles node states: `DEFAULT`, `COMPLETE`, `FAILED`, `AWAITING_INPUT`
|
|
76
|
+
- Key methods:
|
|
77
|
+
- `start(start_node)`: Initialize workflow with starting node
|
|
78
|
+
- `process(input=None)`: Execute nodes until completion or input needed
|
|
79
|
+
- `reset()`: Return to beginning of workflow
|
|
80
|
+
|
|
81
|
+
3. **Node Types** (`core/primitives/`):
|
|
82
|
+
- **TaskNode**: Executes async functions that return bool for success/failure
|
|
83
|
+
- **ConditionalNode**: Branching logic that dynamically inserts next node based on condition
|
|
84
|
+
- Uses `Cursor.insert()` to modify the linked list at runtime
|
|
85
|
+
- Must call `set_condition(condition_func, true_node, false_node)` before execution
|
|
86
|
+
|
|
87
|
+
4. **Data Flow** (`core/base/models.py`):
|
|
88
|
+
- **ListData**: Dictionary-like container passed between nodes
|
|
89
|
+
- Provides `get()`, `update()`, `has()`, and bracket notation access
|
|
90
|
+
- **NodeResult**: Return value from node execution
|
|
91
|
+
- `success`: bool indicating execution success
|
|
92
|
+
- `data`: dict merged into shared ListData
|
|
93
|
+
- `is_awaiting_input`: pauses workflow for external input
|
|
94
|
+
- `message`: contextual information
|
|
95
|
+
|
|
96
|
+
5. **Cursor** (`core/cursor.py`): Utility for linked-list manipulation
|
|
97
|
+
- `Cursor.insert(target, new_node)`: Inserts new_node after target
|
|
98
|
+
- Used by ConditionalNode to dynamically modify workflow structure
|
|
99
|
+
|
|
100
|
+
### Data Structures (`twpm/dsa/`)
|
|
101
|
+
|
|
102
|
+
Contains learning implementations:
|
|
103
|
+
- `linkedlist.py`: Singly-linked list implementation
|
|
104
|
+
- `doublelinkedlist.py`: Doubly-linked list implementation
|
|
105
|
+
|
|
106
|
+
### Testing Pattern
|
|
107
|
+
|
|
108
|
+
Tests use mock nodes with configurable behavior:
|
|
109
|
+
- `MockNode`: Configurable success/failure, data output, awaiting_input state
|
|
110
|
+
- `FailingNode`: Raises exceptions to test error handling
|
|
111
|
+
- `QuestionNode`: Demonstrates input-awaiting pattern with validation
|
|
112
|
+
|
|
113
|
+
All tests are async using `@pytest.mark.asyncio`.
|
|
114
|
+
|
|
115
|
+
## Important Implementation Details
|
|
116
|
+
|
|
117
|
+
### Safe Execution Decorator
|
|
118
|
+
All node `execute()` methods must be decorated with `@safe_execute()`. This decorator:
|
|
119
|
+
- Catches exceptions and converts them to failed NodeResult
|
|
120
|
+
- Prevents exceptions from propagating to orchestrator
|
|
121
|
+
- Logs errors with node identifier
|
|
122
|
+
|
|
123
|
+
### Workflow Pause/Resume Pattern
|
|
124
|
+
Nodes can pause workflow execution by returning `NodeResult(is_awaiting_input=True)`:
|
|
125
|
+
1. First `process()` call: node returns `is_awaiting_input=True`, status becomes `AWAITING_INPUT`
|
|
126
|
+
2. Orchestrator stops, current node remains set
|
|
127
|
+
3. Second `process(input="...")` call: orchestrator provides input via `ListData["_user_input"]`
|
|
128
|
+
4. Node processes input and returns regular result to continue workflow
|
|
129
|
+
|
|
130
|
+
### Dynamic Workflow Modification
|
|
131
|
+
ConditionalNode demonstrates runtime workflow structure changes:
|
|
132
|
+
- Evaluates condition function
|
|
133
|
+
- Uses `Cursor.insert()` to inject appropriate branch into linked list
|
|
134
|
+
- Allows for dynamic, data-driven workflow paths
|
|
135
|
+
|
|
136
|
+
### Node Status Lifecycle
|
|
137
|
+
- `DEFAULT`: Ready to execute (reset before each execution)
|
|
138
|
+
- `COMPLETE`: Successfully executed
|
|
139
|
+
- `FAILED`: Execution failed (stops workflow)
|
|
140
|
+
- `AWAITING_INPUT`: Paused waiting for input (workflow can resume)
|
twpm-0.2.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jackson Vieira
|
|
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.
|
twpm-0.2.3/PKG-INFO
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: twpm
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: A simple workflow builder that takes the complexity out of building dynamic workflows
|
|
5
|
+
Project-URL: Homepage, https://github.com/jacksonvieiracs/twpm
|
|
6
|
+
Project-URL: Repository, https://github.com/jacksonvieiracs/twpm
|
|
7
|
+
Project-URL: Issues, https://github.com/jacksonvieiracs/twpm/issues
|
|
8
|
+
Author-email: Jackson Vieira <dev.jacksonvieira@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: async,n8n as code,orchestrator,task-orchestration,workflow,workflow-engine
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Twpm
|
|
27
|
+
|
|
28
|
+
A simple workflow builder that takes the complexity out of building dynamic workflows.
|
|
29
|
+
|
|
30
|
+
## Motivation
|
|
31
|
+
|
|
32
|
+
Twpm (twelve pm) is a simple workflow engine inspired by n8n, but now using all the benefits that Python offers. Build workflows where you control everything, and they can grow organically.
|
|
33
|
+
|
|
34
|
+
The project was born from building several WhatsApp automation workflows. Each solution was unique, highly coupled, and had significant code duplication. Twpm solves this by providing a framework to create workflows that are fast to build, scalable, and highly decoupled.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
Get started with Twpm in just a few lines of code:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
from twpm.core import Chain, Orchestrator
|
|
43
|
+
from twpm.core.container import Container, ServiceScope
|
|
44
|
+
from twpm.core.primitives import DisplayMessageNode, QuestionNode, SummaryNode
|
|
45
|
+
from twpm.core.depedencies import Output
|
|
46
|
+
|
|
47
|
+
workflow = (
|
|
48
|
+
Chain()
|
|
49
|
+
.add(DisplayMessageNode("Welcome!", key="welcome"))
|
|
50
|
+
.add(QuestionNode("What's your name?", key="name"))
|
|
51
|
+
.add(QuestionNode("What's your email?", key="email"))
|
|
52
|
+
.add(SummaryNode(
|
|
53
|
+
title="Thank you!",
|
|
54
|
+
fields=[("Name", "name"), ("Email", "email")],
|
|
55
|
+
key="summary"
|
|
56
|
+
))
|
|
57
|
+
.build()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
class ConsoleOutput:
|
|
61
|
+
async def send_text(self, text: str):
|
|
62
|
+
print(text, end=" ")
|
|
63
|
+
|
|
64
|
+
async def main():
|
|
65
|
+
container = Container()
|
|
66
|
+
container.registry(Output, lambda: ConsoleOutput(), ServiceScope.SINGLETON)
|
|
67
|
+
orchestrator = Orchestrator(container)
|
|
68
|
+
|
|
69
|
+
orchestrator.start(workflow)
|
|
70
|
+
await orchestrator.process()
|
|
71
|
+
|
|
72
|
+
asyncio.run(main())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
That's it! Twpm handles the rest!
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### Using uv
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
uv add twpm
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Using pip
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pip install twpm
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
- **Simple API**: Build workflows using an intuitive builder pattern or simple function calls
|
|
94
|
+
- **Async Support**: Built on Python's asyncio for efficient async workflows
|
|
95
|
+
- **Dependency Injection**: Lightweight IoC container for clean dependency management
|
|
96
|
+
- **Built-in Primitives**: Ready-to-use nodes for common patterns:
|
|
97
|
+
- `DisplayMessageNode`: Display messages to users
|
|
98
|
+
- `QuestionNode`: Prompt for user input
|
|
99
|
+
- `PoolNode`: Multiple choice questions
|
|
100
|
+
- `QuizNode`: Quiz questions with correct answers
|
|
101
|
+
- `ConditionalNode`: Dynamic branching based on conditions
|
|
102
|
+
- `ProgressNode`: Show progress through workflow steps
|
|
103
|
+
- `SummaryNode`: Display collected data summaries
|
|
104
|
+
- `TaskNode`: Execute custom async tasks
|
|
105
|
+
- **Type Safety**: Full type hints for better IDE support and error detection
|
|
106
|
+
- **Extensible**: Easy to create custom nodes for your specific needs
|
|
107
|
+
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
Check out the [examples directory](examples/):
|
|
111
|
+
|
|
112
|
+
- **CLI Example** (`examples/cli/main.py`): A complete interactive CLI workflow demonstrating:
|
|
113
|
+
- User input collection
|
|
114
|
+
- Progress tracking
|
|
115
|
+
- Data summarization
|
|
116
|
+
- Quiz workflow with conditional routing based on results
|
|
117
|
+
|
|
118
|
+
Run the example:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
git clone https://github.com/jacksonvieiracs/twpm
|
|
122
|
+
cd twpm
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
uv run python3 examples/cli/main.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Try the quiz workflow:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
uv run python3 examples/cli/main.py --quiz
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Architecture
|
|
136
|
+
|
|
137
|
+
### Fundamentals
|
|
138
|
+
|
|
139
|
+

|
|
140
|
+
|
|
141
|
+
Approximately 40% of the codebase is a doubly-linked list implementation. Why use a linked list specifically? It guarantees a level of decoupling between components. Each node can define how the next node is processed, with the orchestrator intervening only when necessary. A node a stateful unit that stores state and contains the full logic. The magic happens when combining linked list fundamentals with an orchestrator that manages routing logic to advance, stop, or await.
|
|
142
|
+
|
|
143
|
+

|
|
144
|
+
|
|
145
|
+
This example demonstrates the benefits of using a linked list data structure for controlling flow behavior. You can create a "condition node" that acts as a router to the next node in your flow based on a computed dynamic condition for example, based on the previous node's result.
|
|
146
|
+
|
|
147
|
+
### Components
|
|
148
|
+
|
|
149
|
+

|
|
150
|
+
|
|
151
|
+
#### Node
|
|
152
|
+
|
|
153
|
+
A Node is a stateful unit of execution. Each node can define guards, execute custom logic, and dynamically choose the next node. Nodes form a double-linked structure (`prev`/`next`) enabling flexible routing based on runtime conditions.
|
|
154
|
+
|
|
155
|
+
#### Chain
|
|
156
|
+
|
|
157
|
+
A Chain is a double-linked list of nodes. It simplifies constructing pipelines by automatically connecting nodes and providing structural operations.
|
|
158
|
+
|
|
159
|
+
#### Orchestrator
|
|
160
|
+
|
|
161
|
+
The Orchestrator coordinates node execution. It decides which node should run next and whether to continue, await input, or stop, based on node results.
|
|
162
|
+
|
|
163
|
+
#### Cursor
|
|
164
|
+
|
|
165
|
+
The Cursor performs operations on the chain knowing only the "current node." It safely manages list mutations (insertion, replacement, deletion) without breaking the chain.
|
|
166
|
+
|
|
167
|
+
#### Container
|
|
168
|
+
|
|
169
|
+
The Container is a lightweight IoC system for injecting dependencies into nodes. It registers services, resolves them when needed, and manages lifecycles, without adding business logic.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|