staged-pipeline 0.1.0a1__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.
- staged_pipeline-0.1.0a1/.editorconfig +18 -0
- staged_pipeline-0.1.0a1/.env.example +9 -0
- staged_pipeline-0.1.0a1/.gitattributes +6 -0
- staged_pipeline-0.1.0a1/.github/dependabot.yml +10 -0
- staged_pipeline-0.1.0a1/.github/workflows/check.yml +59 -0
- staged_pipeline-0.1.0a1/.github/workflows/release.yml +83 -0
- staged_pipeline-0.1.0a1/.gitignore +29 -0
- staged_pipeline-0.1.0a1/.pre-commit-config.yaml +76 -0
- staged_pipeline-0.1.0a1/.python-version +1 -0
- staged_pipeline-0.1.0a1/AGENTS.md +35 -0
- staged_pipeline-0.1.0a1/CHANGELOG.md +33 -0
- staged_pipeline-0.1.0a1/CLAUDE.md +2 -0
- staged_pipeline-0.1.0a1/LICENSE +21 -0
- staged_pipeline-0.1.0a1/PKG-INFO +123 -0
- staged_pipeline-0.1.0a1/README.md +97 -0
- staged_pipeline-0.1.0a1/docs/AUDIT.md +60 -0
- staged_pipeline-0.1.0a1/docs/architecture.md +545 -0
- staged_pipeline-0.1.0a1/docs/guide.md +377 -0
- staged_pipeline-0.1.0a1/docs/plan/DECISIONS.md +88 -0
- staged_pipeline-0.1.0a1/docs/plan/PLAN.md +381 -0
- staged_pipeline-0.1.0a1/docs/plan/PROGRESS.md +181 -0
- staged_pipeline-0.1.0a1/docs/research.md +120 -0
- staged_pipeline-0.1.0a1/examples/crawler.py +32 -0
- staged_pipeline-0.1.0a1/examples/export.py +63 -0
- staged_pipeline-0.1.0a1/pyproject.toml +260 -0
- staged_pipeline-0.1.0a1/scripts/github_bootstrap.py +153 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/__init__.py +81 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_accounting.py +271 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_clock.py +112 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_errors.py +91 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_graph.py +397 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_inbox.py +213 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_limit.py +116 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_pipeline.py +334 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_policy.py +322 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_progress.py +377 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_stage.py +526 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/_supervisor.py +78 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/integrations/__init__.py +1 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/integrations/tallyho.py +139 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/py.typed +0 -0
- staged_pipeline-0.1.0a1/src/staged_pipeline/testing.py +55 -0
- staged_pipeline-0.1.0a1/staged-pipeline-library.md +282 -0
- staged_pipeline-0.1.0a1/tests/conftest.py +12 -0
- staged_pipeline-0.1.0a1/tests/test_accounting.py +207 -0
- staged_pipeline-0.1.0a1/tests/test_audit_fixes.py +99 -0
- staged_pipeline-0.1.0a1/tests/test_clock.py +150 -0
- staged_pipeline-0.1.0a1/tests/test_dedup.py +174 -0
- staged_pipeline-0.1.0a1/tests/test_errors.py +55 -0
- staged_pipeline-0.1.0a1/tests/test_examples.py +27 -0
- staged_pipeline-0.1.0a1/tests/test_graph.py +246 -0
- staged_pipeline-0.1.0a1/tests/test_inbox.py +213 -0
- staged_pipeline-0.1.0a1/tests/test_limits.py +105 -0
- staged_pipeline-0.1.0a1/tests/test_pipeline.py +189 -0
- staged_pipeline-0.1.0a1/tests/test_policy.py +237 -0
- staged_pipeline-0.1.0a1/tests/test_progress.py +196 -0
- staged_pipeline-0.1.0a1/tests/test_routing.py +134 -0
- staged_pipeline-0.1.0a1/tests/test_skeleton.py +8 -0
- staged_pipeline-0.1.0a1/tests/test_stop.py +127 -0
- staged_pipeline-0.1.0a1/tests/test_tallyho.py +103 -0
- staged_pipeline-0.1.0a1/tests/test_testing.py +52 -0
- staged_pipeline-0.1.0a1/tests/test_worker_policies.py +186 -0
- staged_pipeline-0.1.0a1/uv.lock +918 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
indent_style = space
|
|
9
|
+
indent_size = 4
|
|
10
|
+
|
|
11
|
+
[*.{yml,yaml,toml,json}]
|
|
12
|
+
indent_size = 2
|
|
13
|
+
|
|
14
|
+
[*.md]
|
|
15
|
+
trim_trailing_whitespace = false
|
|
16
|
+
|
|
17
|
+
[Makefile]
|
|
18
|
+
indent_style = tab
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Шаблон окружения. Скопировать в .env (в .gitignore) и заполнить.
|
|
2
|
+
# Используется scripts/github_bootstrap.py (poe github), см. PLAN.md T7.1.
|
|
3
|
+
|
|
4
|
+
# Fine-grained PAT на один репозиторий: Administration RW, Contents RW,
|
|
5
|
+
# Workflows RW, Environments RW, Metadata R.
|
|
6
|
+
GH_TOKEN=
|
|
7
|
+
GITHUB_OWNER=0cherednoq
|
|
8
|
+
GITHUB_REPO=staged-pipeline
|
|
9
|
+
GITHUB_VISIBILITY=public
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_call:
|
|
8
|
+
|
|
9
|
+
# UTF-8 вывод на всех ОС: инструменты (import-linter, pytest) печатают русский
|
|
10
|
+
# текст, а Windows-консоль по умолчанию не UTF-8 → UnicodeEncodeError.
|
|
11
|
+
env:
|
|
12
|
+
PYTHONUTF8: "1"
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: check-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
check:
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
os: [ubuntu-latest, windows-latest]
|
|
24
|
+
runs-on: ${{ matrix.os }}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.13"
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- run: uv sync --locked
|
|
32
|
+
- run: uv run poe check
|
|
33
|
+
- run: uv run poe deps
|
|
34
|
+
- run: uv run poe spell
|
|
35
|
+
- name: pre-commit (локальные хуки уже покрыты poe check)
|
|
36
|
+
run: uv run pre-commit run --all-files --show-diff-on-failure
|
|
37
|
+
env:
|
|
38
|
+
SKIP: ruff-format,ruff-check,mypy,import-linter,pytest
|
|
39
|
+
|
|
40
|
+
py314:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
- uses: astral-sh/setup-uv@v6
|
|
45
|
+
with:
|
|
46
|
+
python-version: "3.14"
|
|
47
|
+
enable-cache: true
|
|
48
|
+
- run: uv sync --locked
|
|
49
|
+
- run: uv run poe test
|
|
50
|
+
|
|
51
|
+
build:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: astral-sh/setup-uv@v6
|
|
56
|
+
- run: uv build
|
|
57
|
+
- run: uv run --with twine twine check --strict dist/*
|
|
58
|
+
- name: smoke-импорт колеса в чистом окружении
|
|
59
|
+
run: uv run --isolated --no-project --find-links dist --with staged-pipeline python -c "import staged_pipeline; print(staged_pipeline.__version__)"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
PYTHONUTF8: "1"
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: release-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
check:
|
|
16
|
+
uses: ./.github/workflows/check.yml
|
|
17
|
+
|
|
18
|
+
build:
|
|
19
|
+
needs: check
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v6
|
|
24
|
+
- run: uv build
|
|
25
|
+
- name: тег совпадает с версией
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
ver=$(uv run --no-sync python -c "import tomllib, pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text('utf-8'))['project']['version'])")
|
|
29
|
+
if [ "$tag" != "$ver" ]; then
|
|
30
|
+
echo "тег '$tag' не совпадает с версией в pyproject '$ver'"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
- run: uv run --with twine twine check --strict dist/*
|
|
34
|
+
- uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
|
|
39
|
+
smoke:
|
|
40
|
+
needs: build
|
|
41
|
+
strategy:
|
|
42
|
+
fail-fast: false
|
|
43
|
+
matrix:
|
|
44
|
+
os: [ubuntu-latest, windows-latest]
|
|
45
|
+
py: ["3.13", "3.14"]
|
|
46
|
+
runs-on: ${{ matrix.os }}
|
|
47
|
+
steps:
|
|
48
|
+
- uses: astral-sh/setup-uv@v6
|
|
49
|
+
with:
|
|
50
|
+
python-version: ${{ matrix.py }}
|
|
51
|
+
- uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist
|
|
55
|
+
- run: uv run --isolated --no-project --find-links dist --with staged-pipeline python -c "import staged_pipeline; print(staged_pipeline.__version__)"
|
|
56
|
+
|
|
57
|
+
publish:
|
|
58
|
+
needs: smoke
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
environment: pypi
|
|
61
|
+
permissions:
|
|
62
|
+
id-token: write
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/download-artifact@v4
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist
|
|
68
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
69
|
+
|
|
70
|
+
github-release:
|
|
71
|
+
needs: publish
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
contents: write
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/download-artifact@v4
|
|
77
|
+
with:
|
|
78
|
+
name: dist
|
|
79
|
+
path: dist
|
|
80
|
+
- name: GitHub Release
|
|
81
|
+
run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes
|
|
82
|
+
env:
|
|
83
|
+
GH_TOKEN: ${{ github.token }}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
|
|
13
|
+
# Тестирование и покрытие
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.hypothesis/
|
|
16
|
+
.coverage
|
|
17
|
+
.coverage.*
|
|
18
|
+
coverage.xml
|
|
19
|
+
htmlcov/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.basedpyright/
|
|
23
|
+
|
|
24
|
+
# Секреты
|
|
25
|
+
.env
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Хуки pre-commit. На CI (T6.1) локальные хуки пропускаются (SKIP), т.к. их
|
|
2
|
+
# запускает уже `uv run poe check`. Локально оффлайн первый запуск недоступен:
|
|
3
|
+
# pre-commit клонирует репозитории хуков с GitHub (D-009) — это делается на CI.
|
|
4
|
+
minimum_pre_commit_version: "4.0.0"
|
|
5
|
+
default_install_hook_types: [pre-commit, pre-push]
|
|
6
|
+
|
|
7
|
+
repos:
|
|
8
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
9
|
+
rev: v6.0.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: check-added-large-files
|
|
12
|
+
- id: check-case-conflict
|
|
13
|
+
- id: check-merge-conflict
|
|
14
|
+
- id: check-toml
|
|
15
|
+
- id: check-yaml
|
|
16
|
+
- id: end-of-file-fixer
|
|
17
|
+
- id: mixed-line-ending
|
|
18
|
+
args: [--fix=lf]
|
|
19
|
+
- id: trailing-whitespace
|
|
20
|
+
|
|
21
|
+
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
22
|
+
rev: 0.10.12
|
|
23
|
+
hooks:
|
|
24
|
+
- id: uv-lock
|
|
25
|
+
|
|
26
|
+
- repo: https://github.com/codespell-project/codespell
|
|
27
|
+
rev: v2.4.3
|
|
28
|
+
hooks:
|
|
29
|
+
- id: codespell
|
|
30
|
+
additional_dependencies: [tomli]
|
|
31
|
+
|
|
32
|
+
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
33
|
+
rev: 0.38.2
|
|
34
|
+
hooks:
|
|
35
|
+
- id: check-github-workflows
|
|
36
|
+
|
|
37
|
+
- repo: https://github.com/abravalheri/validate-pyproject
|
|
38
|
+
rev: v0.27
|
|
39
|
+
hooks:
|
|
40
|
+
- id: validate-pyproject
|
|
41
|
+
|
|
42
|
+
# Локальные хуки через uv run --frozen (на CI пропускаются через SKIP).
|
|
43
|
+
- repo: local
|
|
44
|
+
hooks:
|
|
45
|
+
- id: ruff-format
|
|
46
|
+
name: ruff format
|
|
47
|
+
entry: uv run --frozen ruff format --check
|
|
48
|
+
language: system
|
|
49
|
+
types_or: [python, pyi]
|
|
50
|
+
require_serial: true
|
|
51
|
+
- id: ruff-check
|
|
52
|
+
name: ruff check
|
|
53
|
+
entry: uv run --frozen ruff check
|
|
54
|
+
language: system
|
|
55
|
+
types_or: [python, pyi]
|
|
56
|
+
require_serial: true
|
|
57
|
+
- id: mypy
|
|
58
|
+
name: mypy
|
|
59
|
+
entry: uv run --frozen mypy
|
|
60
|
+
language: system
|
|
61
|
+
types_or: [python, pyi]
|
|
62
|
+
pass_filenames: false
|
|
63
|
+
require_serial: true
|
|
64
|
+
- id: import-linter
|
|
65
|
+
name: import-linter
|
|
66
|
+
entry: uv run --frozen lint-imports
|
|
67
|
+
language: system
|
|
68
|
+
pass_filenames: false
|
|
69
|
+
require_serial: true
|
|
70
|
+
- id: pytest
|
|
71
|
+
name: pytest
|
|
72
|
+
entry: uv run --frozen pytest
|
|
73
|
+
language: system
|
|
74
|
+
pass_filenames: false
|
|
75
|
+
require_serial: true
|
|
76
|
+
stages: [pre-push]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Правила разработки staged-pipeline
|
|
2
|
+
|
|
3
|
+
Полный процесс — [docs/plan/PLAN.md](docs/plan/PLAN.md). Ниже — то, что нельзя нарушать (§0.3).
|
|
4
|
+
|
|
5
|
+
## Железные правила
|
|
6
|
+
|
|
7
|
+
- **Не ослаблять** линтеры, типизацию, покрытие (≥ 90%). Подавление — точечное и с причиной:
|
|
8
|
+
`# ruff: ignore[rule] # причина`, `# pyright: ignore[rule] # причина`.
|
|
9
|
+
- Запрещены (banned-api): `typing.Any`, `asyncio.get_event_loop`, `logging.basicConfig`,
|
|
10
|
+
`unittest.mock.patch`.
|
|
11
|
+
- **Ядро без зависимостей:** `dependencies = []`. Extras не импортируют внешних пакетов
|
|
12
|
+
(интеграция tallyho — через `Protocol`, architecture.md §9.3).
|
|
13
|
+
- Все исключения библиотеки — подклассы `StagedPipelineError`, кроме исходов `Skip`/`Error`
|
|
14
|
+
(они — `Outcome(Exception)`, архитектурно отдельны). Все модули объявляют `__all__`.
|
|
15
|
+
- Комментарии и docstring — на русском, идентификаторы — на английском. Google-style docstring.
|
|
16
|
+
- Решения architecture.md не менять молча: сначала запись в
|
|
17
|
+
[docs/plan/DECISIONS.md](docs/plan/DECISIONS.md), потом код.
|
|
18
|
+
- Файлы, которые пишет Python, — с `newline="\n"`.
|
|
19
|
+
|
|
20
|
+
## Время и тесты
|
|
21
|
+
|
|
22
|
+
- Время — только через `Clock`; в тестах `FakeClock`. Реальные `asyncio.sleep` ≤ 10 мс и только там,
|
|
23
|
+
где проверяется сам event loop.
|
|
24
|
+
- Весь набор тестов < 30 с, `pytest-timeout = 5` на тест. Нет Docker, нагрузочных и хаос-тестов.
|
|
25
|
+
|
|
26
|
+
## Гейты (Definition of Done)
|
|
27
|
+
|
|
28
|
+
- `uv run poe check` — формат, линт, типы, import-linter, deptry, codespell, pytest + покрытие.
|
|
29
|
+
- `uv run pre-commit run --all-files`.
|
|
30
|
+
- DoD конкретной карточки задачи в PLAN.md.
|
|
31
|
+
|
|
32
|
+
## Коммиты
|
|
33
|
+
|
|
34
|
+
- Маленькие, одно логическое изменение. Сообщение на русском, заголовок `T<id>: <что>` ≤ 72 символов.
|
|
35
|
+
- Ветка работы — `impl/v1`; коммиты в `main` запрещены pre-commit-хуком.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Все заметные изменения этого проекта документируются здесь.
|
|
4
|
+
|
|
5
|
+
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
|
6
|
+
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0a1] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
Первый альфа-релиз. Полный API v0.1 (architecture.md §4).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Ядро конвейера: `Pipeline`/`Stage`, пул воркеров на стадию, супервизор (fail-fast,
|
|
17
|
+
плоское исключение), вывод режима завершения из графа (DAG по писателям, цикл по quiescence).
|
|
18
|
+
- Политики исходов: `retry=` с backoff (`Expo`/`Fixed`), `skip_on`/`error_on`, `Skip`/`Error`,
|
|
19
|
+
предохранители `ErrorRate`/`NoSuccess`.
|
|
20
|
+
- Дедупликация (`key=`, `SeenStore`), `seed`/`resume`/`seed_open` (`Feeder`).
|
|
21
|
+
- Маршрутизация по типу выхода, `executor="thread"`, лимиты (`Limit`: concurrency + rate).
|
|
22
|
+
- Остановка: `cancel`/`drain`/таймаут.
|
|
23
|
+
- Прогресс: `view`/`watch`/`PipelineReport`, EWMA rate/latency, `expected`/`eta`, `Observer`.
|
|
24
|
+
- `staged_pipeline.testing` (`run_stage`, `FakeClock`), интеграция `integrations.tallyho`.
|
|
25
|
+
- CI (check/release), документация (README, docs/guide.md, docs/architecture.md).
|
|
26
|
+
|
|
27
|
+
### Known limitations (alpha)
|
|
28
|
+
|
|
29
|
+
- Открытые пункты аудита (docs/AUDIT.md): V1 (гонка `ctx.incr` из sync-хендлера), V3, V5–V12.
|
|
30
|
+
- API может меняться до 1.0.
|
|
31
|
+
|
|
32
|
+
[Unreleased]: https://github.com/0cherednoq/staged-pipeline/compare/v0.1.0a1...HEAD
|
|
33
|
+
[0.1.0a1]: https://github.com/0cherednoq/staged-pipeline/releases/tag/v0.1.0a1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 0cherednoq
|
|
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,123 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: staged-pipeline
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Декларативный конвейер стадий поверх asyncio: воркеры, dedup, ретраи, прогресс
|
|
5
|
+
Project-URL: Homepage, https://github.com/0cherednoq/staged-pipeline
|
|
6
|
+
Project-URL: Repository, https://github.com/0cherednoq/staged-pipeline
|
|
7
|
+
Project-URL: Issues, https://github.com/0cherednoq/staged-pipeline/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/0cherednoq/staged-pipeline/blob/main/CHANGELOG.md
|
|
9
|
+
Author: 0cherednoq
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: asyncio,dataflow,pipeline,seda
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.13
|
|
24
|
+
Provides-Extra: tallyho
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# staged-pipeline
|
|
28
|
+
|
|
29
|
+
> **Статус: pre-release (0.1.0a).** API описан в [docs/architecture.md](docs/architecture.md),
|
|
30
|
+
> реализация — по [docs/plan/PLAN.md](docs/plan/PLAN.md). До релиза `0.1.0` возможны изменения.
|
|
31
|
+
|
|
32
|
+
Декларативный конвейер стадий поверх `asyncio` внутри одного процесса: пул воркеров на стадию,
|
|
33
|
+
дедупликация, ретраи с backoff, предохранители, лимиты и живой прогресс. Режим завершения выводится
|
|
34
|
+
из графа (DAG закрывается по писателям, цикл — по quiescence), поэтому «выбрать неправильный режим и
|
|
35
|
+
повиснуть» нельзя.
|
|
36
|
+
|
|
37
|
+
## Чем это является
|
|
38
|
+
|
|
39
|
+
- слой внутри одного процесса и одного event loop: стадии с воркерами, маршрутизацией по типу,
|
|
40
|
+
политиками исходов (`retry`/`skip_on`/`error_on`), лимитами и дедупликацией;
|
|
41
|
+
- источник честного прогресса: `found / queued / in_flight / deferred / ok / skip / error /
|
|
42
|
+
cancelled / duplicates`, `expected` и `eta`;
|
|
43
|
+
- одноразовый граф, проверяемый до старта (ошибка топологии не запускает ничего).
|
|
44
|
+
|
|
45
|
+
## Чем это не является
|
|
46
|
+
|
|
47
|
+
- не durable и не распределённая система — всё в одном процессе;
|
|
48
|
+
- не владеет хранилищем: устойчивость между прогонами даёт БД домена (resume засевом);
|
|
49
|
+
- не замена очереди задач (Tallyho и подобных) — это слой внутри одного Item.
|
|
50
|
+
|
|
51
|
+
## Установка
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv add staged-pipeline # ядро, без зависимостей
|
|
55
|
+
uv add "staged-pipeline[tallyho]" # + маркер интеграции с Tallyho
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Требуется Python 3.13+.
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import asyncio
|
|
64
|
+
from staged_pipeline import Pipeline
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def scan(folder: str): # async-генератор: потоковый fan-out
|
|
68
|
+
for i in range(3):
|
|
69
|
+
yield f"{folder}/{i}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def resolve(path: str) -> list[str]: # атомарный fan-out
|
|
73
|
+
return [f"{path}.a", f"{path}.b"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def download(url: str) -> None: # терминальная стадия
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main() -> None:
|
|
81
|
+
pipe = Pipeline("export")
|
|
82
|
+
listing = pipe.stage("list", scan, workers=2)
|
|
83
|
+
resolving = pipe.stage("resolve", resolve, workers=8, key=lambda p: p)
|
|
84
|
+
media = pipe.stage("media", download, workers=4, key=lambda u: u)
|
|
85
|
+
|
|
86
|
+
listing.to(resolving)
|
|
87
|
+
resolving.to(media)
|
|
88
|
+
listing.seed(["inbox"])
|
|
89
|
+
|
|
90
|
+
report = await pipe.run()
|
|
91
|
+
print(report.stages["media"].ok)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
asyncio.run(main())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Больше примеров — в [examples/](examples/): `crawler.py` (цикл/самопетля) и `export.py`
|
|
98
|
+
(fan-out + dedup + resume). Оба исполняются в тестах.
|
|
99
|
+
|
|
100
|
+
## Когда этапы Tallyho, а когда пайплайн
|
|
101
|
+
|
|
102
|
+
| Критерий | Этапы Tallyho (`sub_batch` + `fed_by`) | Пайплайн внутри Item |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Элемент должен пережить падение воркера сам по себе | ✔ | ✘ (resume — через БД домена) |
|
|
105
|
+
| Элементы дешёвые (≪ 100 мс) и их 10⁴–10⁶ на единицу | дорого: строка + коммит на элемент | ✔ |
|
|
106
|
+
| Общая сессия/лиз/соединение на единицу | ✘ (лиз на задачу) | ✔ |
|
|
107
|
+
| Масштаб на несколько машин | ✔ | ✘ |
|
|
108
|
+
| Нужен точный итог по каждому элементу в БД | ✔ | домен пишет сам |
|
|
109
|
+
|
|
110
|
+
Мост прогресса и остановки к Tallyho — `staged_pipeline.integrations.tallyho` (`track`,
|
|
111
|
+
`outcome_for`), без зависимости от `tallyho`. Подробности — [architecture.md §9](docs/architecture.md).
|
|
112
|
+
|
|
113
|
+
## Документация
|
|
114
|
+
|
|
115
|
+
- [Руководство по задачам (юз-кейсы с примерами)](docs/guide.md)
|
|
116
|
+
- [Архитектура и API](docs/architecture.md)
|
|
117
|
+
- [Prior art / ресёрч](docs/research.md)
|
|
118
|
+
- [План реализации](docs/plan/PLAN.md) · [прогресс](docs/plan/PROGRESS.md) ·
|
|
119
|
+
[решения](docs/plan/DECISIONS.md)
|
|
120
|
+
|
|
121
|
+
## Лицензия
|
|
122
|
+
|
|
123
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# staged-pipeline
|
|
2
|
+
|
|
3
|
+
> **Статус: pre-release (0.1.0a).** API описан в [docs/architecture.md](docs/architecture.md),
|
|
4
|
+
> реализация — по [docs/plan/PLAN.md](docs/plan/PLAN.md). До релиза `0.1.0` возможны изменения.
|
|
5
|
+
|
|
6
|
+
Декларативный конвейер стадий поверх `asyncio` внутри одного процесса: пул воркеров на стадию,
|
|
7
|
+
дедупликация, ретраи с backoff, предохранители, лимиты и живой прогресс. Режим завершения выводится
|
|
8
|
+
из графа (DAG закрывается по писателям, цикл — по quiescence), поэтому «выбрать неправильный режим и
|
|
9
|
+
повиснуть» нельзя.
|
|
10
|
+
|
|
11
|
+
## Чем это является
|
|
12
|
+
|
|
13
|
+
- слой внутри одного процесса и одного event loop: стадии с воркерами, маршрутизацией по типу,
|
|
14
|
+
политиками исходов (`retry`/`skip_on`/`error_on`), лимитами и дедупликацией;
|
|
15
|
+
- источник честного прогресса: `found / queued / in_flight / deferred / ok / skip / error /
|
|
16
|
+
cancelled / duplicates`, `expected` и `eta`;
|
|
17
|
+
- одноразовый граф, проверяемый до старта (ошибка топологии не запускает ничего).
|
|
18
|
+
|
|
19
|
+
## Чем это не является
|
|
20
|
+
|
|
21
|
+
- не durable и не распределённая система — всё в одном процессе;
|
|
22
|
+
- не владеет хранилищем: устойчивость между прогонами даёт БД домена (resume засевом);
|
|
23
|
+
- не замена очереди задач (Tallyho и подобных) — это слой внутри одного Item.
|
|
24
|
+
|
|
25
|
+
## Установка
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv add staged-pipeline # ядро, без зависимостей
|
|
29
|
+
uv add "staged-pipeline[tallyho]" # + маркер интеграции с Tallyho
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Требуется Python 3.13+.
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import asyncio
|
|
38
|
+
from staged_pipeline import Pipeline
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def scan(folder: str): # async-генератор: потоковый fan-out
|
|
42
|
+
for i in range(3):
|
|
43
|
+
yield f"{folder}/{i}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def resolve(path: str) -> list[str]: # атомарный fan-out
|
|
47
|
+
return [f"{path}.a", f"{path}.b"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def download(url: str) -> None: # терминальная стадия
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def main() -> None:
|
|
55
|
+
pipe = Pipeline("export")
|
|
56
|
+
listing = pipe.stage("list", scan, workers=2)
|
|
57
|
+
resolving = pipe.stage("resolve", resolve, workers=8, key=lambda p: p)
|
|
58
|
+
media = pipe.stage("media", download, workers=4, key=lambda u: u)
|
|
59
|
+
|
|
60
|
+
listing.to(resolving)
|
|
61
|
+
resolving.to(media)
|
|
62
|
+
listing.seed(["inbox"])
|
|
63
|
+
|
|
64
|
+
report = await pipe.run()
|
|
65
|
+
print(report.stages["media"].ok)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Больше примеров — в [examples/](examples/): `crawler.py` (цикл/самопетля) и `export.py`
|
|
72
|
+
(fan-out + dedup + resume). Оба исполняются в тестах.
|
|
73
|
+
|
|
74
|
+
## Когда этапы Tallyho, а когда пайплайн
|
|
75
|
+
|
|
76
|
+
| Критерий | Этапы Tallyho (`sub_batch` + `fed_by`) | Пайплайн внутри Item |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Элемент должен пережить падение воркера сам по себе | ✔ | ✘ (resume — через БД домена) |
|
|
79
|
+
| Элементы дешёвые (≪ 100 мс) и их 10⁴–10⁶ на единицу | дорого: строка + коммит на элемент | ✔ |
|
|
80
|
+
| Общая сессия/лиз/соединение на единицу | ✘ (лиз на задачу) | ✔ |
|
|
81
|
+
| Масштаб на несколько машин | ✔ | ✘ |
|
|
82
|
+
| Нужен точный итог по каждому элементу в БД | ✔ | домен пишет сам |
|
|
83
|
+
|
|
84
|
+
Мост прогресса и остановки к Tallyho — `staged_pipeline.integrations.tallyho` (`track`,
|
|
85
|
+
`outcome_for`), без зависимости от `tallyho`. Подробности — [architecture.md §9](docs/architecture.md).
|
|
86
|
+
|
|
87
|
+
## Документация
|
|
88
|
+
|
|
89
|
+
- [Руководство по задачам (юз-кейсы с примерами)](docs/guide.md)
|
|
90
|
+
- [Архитектура и API](docs/architecture.md)
|
|
91
|
+
- [Prior art / ресёрч](docs/research.md)
|
|
92
|
+
- [План реализации](docs/plan/PLAN.md) · [прогресс](docs/plan/PROGRESS.md) ·
|
|
93
|
+
[решения](docs/plan/DECISIONS.md)
|
|
94
|
+
|
|
95
|
+
## Лицензия
|
|
96
|
+
|
|
97
|
+
[MIT](LICENSE).
|