openbox-temporal-sdk-python 1.0.0__tar.gz → 1.0.2__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.
- openbox_temporal_sdk_python-1.0.2/.github/workflows/publish.yml +46 -0
- openbox_temporal_sdk_python-1.0.2/.github/workflows/sonarqube.yaml +182 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/.gitignore +7 -0
- openbox_temporal_sdk_python-1.0.2/.repomixignore +22 -0
- openbox_temporal_sdk_python-1.0.2/PKG-INFO +374 -0
- openbox_temporal_sdk_python-1.0.2/README.md +325 -0
- openbox_temporal_sdk_python-1.0.2/docs/code-standards.md +742 -0
- openbox_temporal_sdk_python-1.0.2/docs/codebase-summary.md +503 -0
- openbox_temporal_sdk_python-1.0.2/docs/configuration.md +63 -0
- openbox_temporal_sdk_python-1.0.2/docs/project-overview-pdr.md +357 -0
- openbox_temporal_sdk_python-1.0.2/docs/system-architecture.md +760 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/__init__.py +2 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/activities.py +1 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/activity_interceptor.py +8 -2
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/config.py +51 -3
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/otel_setup.py +42 -4
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/worker.py +72 -71
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/pyproject.toml +2 -1
- openbox_temporal_sdk_python-1.0.2/release-manifest.json +10908 -0
- openbox_temporal_sdk_python-1.0.2/repomix-output.xml +15911 -0
- openbox_temporal_sdk_python-1.0.2/sonar-project.properties +13 -0
- openbox_temporal_sdk_python-1.0.2/tests/__init__.py +2 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_activities.py +758 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_activity_interceptor.py +2081 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_config.py +979 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_otel_setup.py +1774 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_span_processor.py +1145 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_tracing.py +868 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_types.py +802 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_worker.py +1884 -0
- openbox_temporal_sdk_python-1.0.2/tests/test_workflow_interceptor.py +1350 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/uv.lock +268 -61
- openbox_temporal_sdk_python-1.0.0/.claude/settings.local.json +0 -8
- openbox_temporal_sdk_python-1.0.0/PKG-INFO +0 -1214
- openbox_temporal_sdk_python-1.0.0/PLAN.md +0 -234
- openbox_temporal_sdk_python-1.0.0/README.md +0 -1165
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/.python-version +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/LICENSE +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/py.typed +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/span_processor.py +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/tracing.py +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/types.py +0 -0
- {openbox_temporal_sdk_python-1.0.0 → openbox_temporal_sdk_python-1.0.2}/openbox/workflow_interceptor.py +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Build and Publish
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: release
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # Required for trusted publishing
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.11"
|
|
23
|
+
|
|
24
|
+
- name: Install build dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install hatch twine
|
|
28
|
+
|
|
29
|
+
- name: Verify version matches tag
|
|
30
|
+
run: |
|
|
31
|
+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
|
32
|
+
PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
33
|
+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
34
|
+
echo "Error: Tag version ($TAG_VERSION) does not match package version ($PKG_VERSION)"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
echo "Version verified: $PKG_VERSION"
|
|
38
|
+
|
|
39
|
+
- name: Build package
|
|
40
|
+
run: hatch build
|
|
41
|
+
|
|
42
|
+
- name: Publish to PyPI
|
|
43
|
+
env:
|
|
44
|
+
TWINE_USERNAME: __token__
|
|
45
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
46
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# .github/workflows/sonarqube.yml
|
|
2
|
+
name: SonarQube
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
pull-requests: write
|
|
13
|
+
issues: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
sonarqube:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
env:
|
|
19
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: '3.11'
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: uv sync --all-extras --dev
|
|
36
|
+
|
|
37
|
+
- name: Run tests with coverage
|
|
38
|
+
run: |
|
|
39
|
+
uv run pytest tests/ \
|
|
40
|
+
--cov=openbox \
|
|
41
|
+
--cov-report=xml:coverage.xml \
|
|
42
|
+
--cov-report=term-missing \
|
|
43
|
+
-v
|
|
44
|
+
|
|
45
|
+
- name: Compute Sonar project name
|
|
46
|
+
shell: bash
|
|
47
|
+
run: |
|
|
48
|
+
REPO_NAME="${{ github.event.repository.name }}"
|
|
49
|
+
|
|
50
|
+
if [ "${GITHUB_REF_NAME}" = "staging" ]; then
|
|
51
|
+
echo "SONAR_PROJECT_NAME=${REPO_NAME}-staging" >> $GITHUB_ENV
|
|
52
|
+
else
|
|
53
|
+
echo "SONAR_PROJECT_NAME=${REPO_NAME}" >> $GITHUB_ENV
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
- name: SonarQube Scan
|
|
57
|
+
uses: SonarSource/sonarqube-scan-action@v5.3.0
|
|
58
|
+
env:
|
|
59
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
60
|
+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
61
|
+
with:
|
|
62
|
+
args: >
|
|
63
|
+
-Dsonar.projectKey=${{ env.SONAR_PROJECT_NAME }}
|
|
64
|
+
-Dsonar.projectName="${{ env.SONAR_PROJECT_NAME }}"
|
|
65
|
+
|
|
66
|
+
- name: Quality Gate
|
|
67
|
+
uses: SonarSource/sonarqube-quality-gate-action@v1.2.0
|
|
68
|
+
continue-on-error: true
|
|
69
|
+
env:
|
|
70
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
71
|
+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
72
|
+
with:
|
|
73
|
+
pollingTimeoutSec: 600
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
- name: Wait for quality gate
|
|
77
|
+
if: always()
|
|
78
|
+
run: sleep 30
|
|
79
|
+
|
|
80
|
+
- name: Build SonarQube Quality Gate report
|
|
81
|
+
if: always()
|
|
82
|
+
id: qg
|
|
83
|
+
env:
|
|
84
|
+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
85
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
86
|
+
SONAR_PROJECT_NAME: ${{ env.SONAR_PROJECT_NAME }}
|
|
87
|
+
run: |
|
|
88
|
+
set -euo pipefail
|
|
89
|
+
|
|
90
|
+
HOST_TRIM="${SONAR_HOST_URL%/}"
|
|
91
|
+
TOKEN="${SONAR_TOKEN}"
|
|
92
|
+
PKEY="${SONAR_PROJECT_NAME}" # projectKey
|
|
93
|
+
PNAME="${SONAR_PROJECT_NAME}" # projectName hiển thị
|
|
94
|
+
|
|
95
|
+
urlencode() { jq -nr --arg v "$1" '$v|@uri'; }
|
|
96
|
+
|
|
97
|
+
PKEY_ENC="$(urlencode "$PKEY")"
|
|
98
|
+
API_URL="${HOST_TRIM}/api/qualitygates/project_status?projectKey=${PKEY_ENC}"
|
|
99
|
+
|
|
100
|
+
JSON="$(curl -fsSL -u "${TOKEN}:" "$API_URL")"
|
|
101
|
+
|
|
102
|
+
STATUS="$(jq -r '.projectStatus.status' <<<"$JSON")" # OK | WARN | ERROR
|
|
103
|
+
QG_NAME="$(jq -r '.projectStatus.qualityGate.name // "Default"' <<<"$JSON")"
|
|
104
|
+
if [ -z "${SONAR_HOST_URL:-}" ]; then
|
|
105
|
+
echo "::warning::SONAR_HOST_URL is empty"
|
|
106
|
+
fi
|
|
107
|
+
DASH_URL="${SONAR_HOST_URL}/dashboard?id=$(urlencode "$PKEY")"
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
echo "### SonarQube Quality Gate: ${QG_NAME}"
|
|
111
|
+
echo
|
|
112
|
+
echo "- **Project**: ${PNAME}"
|
|
113
|
+
echo "- **Status**: **${STATUS}**"
|
|
114
|
+
echo "- **Detail**: [Open in SonarQube](${DASH_URL})"
|
|
115
|
+
} > qg.md
|
|
116
|
+
|
|
117
|
+
cat qg.md >> "$GITHUB_STEP_SUMMARY"
|
|
118
|
+
echo "STATUS=${STATUS}" >> "$GITHUB_ENV"
|
|
119
|
+
echo "QG_NAME=${QG_NAME}" >> "$GITHUB_ENV"
|
|
120
|
+
echo "PNAME=${PNAME}" >> "$GITHUB_ENV"
|
|
121
|
+
|
|
122
|
+
# (2) Upsert Issue cho
|
|
123
|
+
- name: Comment (or create) Issue with QG report
|
|
124
|
+
if: always() && env.STATUS != 'OK'
|
|
125
|
+
uses: actions/github-script@v7
|
|
126
|
+
env:
|
|
127
|
+
ISSUE_NUMBER: ${{ vars.SONAR_ISSUE_NUMBER }}
|
|
128
|
+
SONAR_PROJECT_NAME: ${{ env.SONAR_PROJECT_NAME }}
|
|
129
|
+
QG_NAME: ${{ env.QG_NAME }}
|
|
130
|
+
STATUS: ${{ env.STATUS }}
|
|
131
|
+
with:
|
|
132
|
+
script: |
|
|
133
|
+
const fs = require('fs');
|
|
134
|
+
const owner = context.repo.owner;
|
|
135
|
+
const repo = context.repo.repo;
|
|
136
|
+
|
|
137
|
+
const body = fs.readFileSync('qg.md', 'utf8');
|
|
138
|
+
const projectName = process.env.SONAR_PROJECT_NAME;
|
|
139
|
+
const status = process.env.STATUS;
|
|
140
|
+
const qgName = process.env.QG_NAME;
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
const stableTitle = `SonarQube QG • ${projectName}`;
|
|
144
|
+
|
|
145
|
+
const displayTitle = `${stableTitle} — ${qgName} (${status})`;
|
|
146
|
+
|
|
147
|
+
let issueNumber = process.env.ISSUE_NUMBER ? Number(process.env.ISSUE_NUMBER) : null;
|
|
148
|
+
|
|
149
|
+
if (!issueNumber) {
|
|
150
|
+
const { data: search } = await github.rest.search.issuesAndPullRequests({
|
|
151
|
+
q: `repo:${owner}/${repo} is:issue state:open in:title "${stableTitle}" label:sonarqube`
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (search.total_count > 0) {
|
|
155
|
+
issueNumber = search.items[0].number;
|
|
156
|
+
core.info(`Found existing issue #${issueNumber} for project "${projectName}".`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (issueNumber) {
|
|
161
|
+
try {
|
|
162
|
+
await github.rest.issues.update({ owner, repo, issue_number: issueNumber, title: stableTitle });
|
|
163
|
+
} catch (e) {
|
|
164
|
+
core.warning(`Failed to update title to stableTitle: ${e.message}`);
|
|
165
|
+
}
|
|
166
|
+
await github.rest.issues.createComment({ owner, repo, issue_number: issueNumber, body });
|
|
167
|
+
core.info(`Appended comment to issue #${issueNumber}`);
|
|
168
|
+
} else {
|
|
169
|
+
const { data: issue } = await github.rest.issues.create({
|
|
170
|
+
owner, repo,
|
|
171
|
+
title: stableTitle,
|
|
172
|
+
body,
|
|
173
|
+
labels: ['sonarqube','quality-gate']
|
|
174
|
+
});
|
|
175
|
+
core.info(`Created issue #${issue.number} for project "${projectName}"`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
- name: Fail job when Quality Gate not OK
|
|
179
|
+
if: env.STATUS != 'OK'
|
|
180
|
+
run: |
|
|
181
|
+
echo "::error title=Quality Gate Failed::STATUS=${{ env.STATUS }} (expected OK)"
|
|
182
|
+
exit 1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
docs/*
|
|
2
|
+
plans/*
|
|
3
|
+
assets/*
|
|
4
|
+
dist/*
|
|
5
|
+
coverage/*
|
|
6
|
+
build/*
|
|
7
|
+
ios/*
|
|
8
|
+
android/*
|
|
9
|
+
tests/*
|
|
10
|
+
__tests__/*
|
|
11
|
+
__pycache__/*
|
|
12
|
+
node_modules/*
|
|
13
|
+
|
|
14
|
+
.opencode/*
|
|
15
|
+
.claude/*
|
|
16
|
+
.serena/*
|
|
17
|
+
.pnpm-store/*
|
|
18
|
+
.github/*
|
|
19
|
+
.dart_tool/*
|
|
20
|
+
.idea/*
|
|
21
|
+
.husky/*
|
|
22
|
+
.venv/*
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openbox-temporal-sdk-python
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: OpenBox SDK - Governance and observability for Temporal workflows
|
|
5
|
+
Project-URL: Homepage, https://github.com/OpenBox-AI/temporal-sdk-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/OpenBox-AI/temporal-sdk-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/OpenBox-AI/temporal-sdk-python.git
|
|
8
|
+
Project-URL: Issues, https://github.com/OpenBox-AI/temporal-sdk-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/OpenBox-AI/temporal-sdk-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: OpenBox Team <tino@openbox.ai>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: governance,hitl,human-in-the-loop,observability,opentelemetry,temporal,workflow
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
27
|
+
Requires-Dist: httpx<1,>=0.28.0
|
|
28
|
+
Requires-Dist: mysql-connector-python>=8.0.0
|
|
29
|
+
Requires-Dist: opentelemetry-api>=1.38.0
|
|
30
|
+
Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.59b0
|
|
31
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.59b0
|
|
32
|
+
Requires-Dist: opentelemetry-instrumentation-mysql>=0.59b0
|
|
33
|
+
Requires-Dist: opentelemetry-instrumentation-psycopg2>=0.59b0
|
|
34
|
+
Requires-Dist: opentelemetry-instrumentation-pymongo>=0.59b0
|
|
35
|
+
Requires-Dist: opentelemetry-instrumentation-pymysql>=0.59b0
|
|
36
|
+
Requires-Dist: opentelemetry-instrumentation-redis>=0.59b0
|
|
37
|
+
Requires-Dist: opentelemetry-instrumentation-requests>=0.59b0
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.59b0
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-urllib3>=0.59b0
|
|
40
|
+
Requires-Dist: opentelemetry-instrumentation-urllib>=0.59b0
|
|
41
|
+
Requires-Dist: opentelemetry-sdk>=1.38.0
|
|
42
|
+
Requires-Dist: psycopg2-binary>=2.9.10
|
|
43
|
+
Requires-Dist: pymongo>=4.0.0
|
|
44
|
+
Requires-Dist: pymysql>=1.0.0
|
|
45
|
+
Requires-Dist: redis>=5.0.0
|
|
46
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
47
|
+
Requires-Dist: temporalio<2,>=1.8.0
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# OpenBox SDK for Temporal Workflows
|
|
51
|
+
|
|
52
|
+
OpenBox SDK provides **governance and observability** for Temporal workflows by capturing workflow/activity lifecycle events, HTTP telemetry, database queries, and file operations, then sending them to OpenBox Core for policy evaluation.
|
|
53
|
+
|
|
54
|
+
**Key Features:**
|
|
55
|
+
- 6 event types (WorkflowStarted, WorkflowCompleted, WorkflowFailed, SignalReceived, ActivityStarted, ActivityCompleted)
|
|
56
|
+
- 5-tier verdict system (ALLOW, CONSTRAIN, REQUIRE_APPROVAL, BLOCK, HALT)
|
|
57
|
+
- HTTP/Database/File I/O instrumentation via OpenTelemetry
|
|
58
|
+
- Guardrails: Input/output validation and redaction
|
|
59
|
+
- Human-in-the-loop approval with expiration handling
|
|
60
|
+
- Zero-code setup via `create_openbox_worker()` factory
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install openbox-temporal-sdk-python
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Requirements:**
|
|
71
|
+
- Python 3.9+
|
|
72
|
+
- Temporal SDK 1.8+
|
|
73
|
+
- OpenTelemetry API/SDK 1.38.0+
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
Use the `create_openbox_worker()` factory for simple integration:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import os
|
|
83
|
+
from openbox import create_openbox_worker
|
|
84
|
+
|
|
85
|
+
worker = create_openbox_worker(
|
|
86
|
+
client=client,
|
|
87
|
+
task_queue="my-task-queue",
|
|
88
|
+
workflows=[MyWorkflow],
|
|
89
|
+
activities=[my_activity],
|
|
90
|
+
# OpenBox config
|
|
91
|
+
openbox_url=os.getenv("OPENBOX_URL"),
|
|
92
|
+
openbox_api_key=os.getenv("OPENBOX_API_KEY"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
await worker.run()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The factory automatically:
|
|
99
|
+
1. Validates the API key
|
|
100
|
+
2. Creates span processor
|
|
101
|
+
3. Sets up OpenTelemetry instrumentation
|
|
102
|
+
4. Creates governance interceptors
|
|
103
|
+
5. Adds `send_governance_event` activity
|
|
104
|
+
6. Returns fully configured Worker
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
### Environment Variables
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
OPENBOX_URL=http://localhost:8086
|
|
114
|
+
OPENBOX_API_KEY=obx_test_key_1
|
|
115
|
+
OPENBOX_GOVERNANCE_TIMEOUT=30.0
|
|
116
|
+
OPENBOX_GOVERNANCE_POLICY=fail_open # or fail_closed
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Factory Function Parameters
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
worker = create_openbox_worker(
|
|
123
|
+
client=client,
|
|
124
|
+
task_queue="my-task-queue",
|
|
125
|
+
workflows=[MyWorkflow],
|
|
126
|
+
activities=[my_activity],
|
|
127
|
+
|
|
128
|
+
# OpenBox config
|
|
129
|
+
openbox_url="http://localhost:8086",
|
|
130
|
+
openbox_api_key="obx_test_key_1",
|
|
131
|
+
governance_timeout=30.0,
|
|
132
|
+
governance_policy="fail_open",
|
|
133
|
+
|
|
134
|
+
# Event filtering
|
|
135
|
+
send_start_event=True,
|
|
136
|
+
send_activity_start_event=True,
|
|
137
|
+
skip_workflow_types={"InternalWorkflow"},
|
|
138
|
+
skip_activity_types={"send_governance_event"},
|
|
139
|
+
skip_signals={"heartbeat"},
|
|
140
|
+
|
|
141
|
+
# Database instrumentation
|
|
142
|
+
instrument_databases=True,
|
|
143
|
+
db_libraries={"psycopg2", "sqlalchemy"}, # None = all available
|
|
144
|
+
sqlalchemy_engine=engine, # pass pre-existing engine for query capture
|
|
145
|
+
|
|
146
|
+
# File I/O instrumentation
|
|
147
|
+
instrument_file_io=False, # disabled by default
|
|
148
|
+
|
|
149
|
+
# Standard Worker options (all supported)
|
|
150
|
+
activity_executor=my_executor,
|
|
151
|
+
max_concurrent_activities=10,
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Governance Verdicts
|
|
158
|
+
|
|
159
|
+
OpenBox Core returns a verdict indicating what action the SDK should take.
|
|
160
|
+
|
|
161
|
+
| Verdict | Behavior |
|
|
162
|
+
|---------|----------|
|
|
163
|
+
| `ALLOW` | Continue execution normally |
|
|
164
|
+
| `CONSTRAIN` | Log constraints, continue |
|
|
165
|
+
| `REQUIRE_APPROVAL` | Pause, poll for human approval |
|
|
166
|
+
| `BLOCK` | Raise error, stop activity |
|
|
167
|
+
| `HALT` | Raise error, terminate workflow |
|
|
168
|
+
|
|
169
|
+
**v1.0 Backward Compatibility:**
|
|
170
|
+
- `"continue"` → `ALLOW`
|
|
171
|
+
- `"stop"` → `HALT`
|
|
172
|
+
- `"require-approval"` → `REQUIRE_APPROVAL`
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Event Types
|
|
177
|
+
|
|
178
|
+
| Event | Trigger | Captured Fields |
|
|
179
|
+
|-------|---------|-----------------|
|
|
180
|
+
| WorkflowStarted | Workflow begins | workflow_id, run_id, workflow_type, task_queue |
|
|
181
|
+
| WorkflowCompleted | Workflow succeeds | workflow_id, run_id, workflow_type |
|
|
182
|
+
| WorkflowFailed | Workflow fails | workflow_id, run_id, workflow_type, error |
|
|
183
|
+
| SignalReceived | Signal received | workflow_id, signal_name, signal_args |
|
|
184
|
+
| ActivityStarted | Activity begins | activity_id, activity_type, activity_input |
|
|
185
|
+
| ActivityCompleted | Activity ends | activity_id, activity_type, activity_input, activity_output, spans, status, duration |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Guardrails (Input/Output Redaction)
|
|
190
|
+
|
|
191
|
+
OpenBox Core can validate and redact sensitive data before/after activity execution:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
# Request
|
|
195
|
+
{
|
|
196
|
+
"verdict": "allow",
|
|
197
|
+
"guardrails_result": {
|
|
198
|
+
"input_type": "activity_input",
|
|
199
|
+
"redacted_input": {"prompt": "[REDACTED]", "user_id": "123"},
|
|
200
|
+
"validation_passed": true,
|
|
201
|
+
"reasons": []
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# If validation fails:
|
|
206
|
+
{
|
|
207
|
+
"validation_passed": false,
|
|
208
|
+
"reasons": [
|
|
209
|
+
{"type": "pii", "field": "email", "reason": "Contains PII"}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Error Handling
|
|
217
|
+
|
|
218
|
+
Configure error policy via `on_api_error`:
|
|
219
|
+
|
|
220
|
+
| Policy | Behavior |
|
|
221
|
+
|--------|----------|
|
|
222
|
+
| `fail_open` (default) | If governance API fails, allow workflow to continue |
|
|
223
|
+
| `fail_closed` | If governance API fails, terminate workflow |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Supported Instrumentation
|
|
228
|
+
|
|
229
|
+
### HTTP Libraries
|
|
230
|
+
- `httpx` (sync + async) - full body capture
|
|
231
|
+
- `requests` - full body capture
|
|
232
|
+
- `urllib3` - full body capture
|
|
233
|
+
- `urllib` - request body only
|
|
234
|
+
|
|
235
|
+
### Databases
|
|
236
|
+
- PostgreSQL: `psycopg2`, `asyncpg`
|
|
237
|
+
- MySQL: `mysql-connector-python`, `pymysql`
|
|
238
|
+
- MongoDB: `pymongo`
|
|
239
|
+
- Redis: `redis`
|
|
240
|
+
- ORM: `sqlalchemy`
|
|
241
|
+
|
|
242
|
+
**SQLAlchemy Note:** If your SQLAlchemy engine is created before `create_openbox_worker()` runs (e.g., at module import time), you must pass it via the `sqlalchemy_engine` parameter. Without this, `SQLAlchemyInstrumentor` only patches future `create_engine()` calls and won't capture queries on pre-existing engines.
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from db.engine import engine
|
|
246
|
+
|
|
247
|
+
worker = create_openbox_worker(
|
|
248
|
+
...,
|
|
249
|
+
db_libraries={"psycopg2", "sqlalchemy"},
|
|
250
|
+
sqlalchemy_engine=engine,
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### File I/O
|
|
255
|
+
- `open()`, `read()`, `write()`, `readline()`, `readlines()`
|
|
256
|
+
- Skips system paths (`/dev/`, `/proc/`, `/sys/`, `__pycache__`)
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Architecture
|
|
261
|
+
|
|
262
|
+
See [System Architecture](./docs/system-architecture.md) for detailed component design.
|
|
263
|
+
|
|
264
|
+
**High-Level Flow:**
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Workflow/Activity → Interceptors → Span Processor → OpenBox Core API
|
|
268
|
+
↓
|
|
269
|
+
Returns Verdict
|
|
270
|
+
↓
|
|
271
|
+
(ALLOW, BLOCK, HALT, etc.)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Advanced Usage
|
|
277
|
+
|
|
278
|
+
For manual control, import individual components:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from openbox import (
|
|
282
|
+
initialize,
|
|
283
|
+
WorkflowSpanProcessor,
|
|
284
|
+
GovernanceInterceptor,
|
|
285
|
+
GovernanceConfig,
|
|
286
|
+
)
|
|
287
|
+
from openbox.otel_setup import setup_opentelemetry_for_governance
|
|
288
|
+
from openbox.activity_interceptor import ActivityGovernanceInterceptor
|
|
289
|
+
from openbox.activities import send_governance_event
|
|
290
|
+
|
|
291
|
+
# 1. Initialize SDK
|
|
292
|
+
initialize(api_url="http://localhost:8086", api_key="obx_test_key_1")
|
|
293
|
+
|
|
294
|
+
# 2. Create span processor
|
|
295
|
+
span_processor = WorkflowSpanProcessor(
|
|
296
|
+
ignored_url_prefixes=["http://localhost:8086"]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# 3. Setup OTel instrumentation
|
|
300
|
+
setup_opentelemetry_for_governance(
|
|
301
|
+
span_processor,
|
|
302
|
+
sqlalchemy_engine=engine, # optional: instrument pre-existing engine
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# 4. Create governance config
|
|
306
|
+
config = GovernanceConfig(
|
|
307
|
+
on_api_error="fail_closed",
|
|
308
|
+
api_timeout=30.0,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# 5. Create interceptors
|
|
312
|
+
workflow_interceptor = GovernanceInterceptor(
|
|
313
|
+
api_url="http://localhost:8086",
|
|
314
|
+
api_key="obx_test_key_1",
|
|
315
|
+
span_processor=span_processor,
|
|
316
|
+
config=config,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
activity_interceptor = ActivityGovernanceInterceptor(
|
|
320
|
+
api_url="http://localhost:8086",
|
|
321
|
+
api_key="obx_test_key_1",
|
|
322
|
+
span_processor=span_processor,
|
|
323
|
+
config=config,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# 6. Create worker
|
|
327
|
+
from temporalio.worker import Worker
|
|
328
|
+
worker = Worker(
|
|
329
|
+
client=client,
|
|
330
|
+
task_queue="my-task-queue",
|
|
331
|
+
workflows=[MyWorkflow],
|
|
332
|
+
activities=[my_activity, send_governance_event],
|
|
333
|
+
interceptors=[workflow_interceptor, activity_interceptor],
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Documentation
|
|
340
|
+
|
|
341
|
+
- **[Project Overview & PDR](./docs/project-overview-pdr.md)** - Requirements, features, constraints
|
|
342
|
+
- **[System Architecture](./docs/system-architecture.md)** - Component design, data flows, security
|
|
343
|
+
- **[Codebase Summary](./docs/codebase-summary.md)** - Code structure and component details
|
|
344
|
+
- **[Code Standards](./docs/code-standards.md)** - Coding conventions and best practices
|
|
345
|
+
- **[Project Roadmap](./docs/project-roadmap.md)** - Future enhancements and timeline
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Testing
|
|
350
|
+
|
|
351
|
+
The SDK includes comprehensive test coverage with 10 test files:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
pytest tests/
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Test files: `test_activities.py`, `test_activity_interceptor.py`, `test_config.py`, `test_otel_setup.py`, `test_span_processor.py`, `test_tracing.py`, `test_types.py`, `test_worker.py`, `test_workflow_interceptor.py`
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## License
|
|
362
|
+
|
|
363
|
+
MIT License - See LICENSE file for details
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Support
|
|
368
|
+
|
|
369
|
+
- **Issues:** GitHub Issues
|
|
370
|
+
- **Documentation:** See `./docs/`
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
**Version:** 1.0.2 | **Last Updated:** 2026-02-12
|