pyrill 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrill-0.2.0/PKG-INFO +99 -0
- pyrill-0.2.0/README.md +80 -0
- pyrill-0.2.0/pyproject.toml +146 -0
- pyrill-0.2.0/pyproject.toml.orig +132 -0
- pyrill-0.2.0/src/pyrill/__init__.py +0 -0
- pyrill-0.2.0/src/pyrill/accumulators.py +195 -0
- pyrill-0.2.0/src/pyrill/base.py +1244 -0
- pyrill-0.2.0/src/pyrill/bytes.py +593 -0
- pyrill-0.2.0/src/pyrill/chunks.py +1057 -0
- pyrill-0.2.0/src/pyrill/csv.py +628 -0
- pyrill-0.2.0/src/pyrill/debug.py +106 -0
- pyrill-0.2.0/src/pyrill/io.py +870 -0
- pyrill-0.2.0/src/pyrill/json.py +529 -0
- pyrill-0.2.0/src/pyrill/lists.py +157 -0
- pyrill-0.2.0/src/pyrill/mappers.py +287 -0
- pyrill-0.2.0/src/pyrill/primitives.py +1606 -0
- pyrill-0.2.0/src/pyrill/py.typed +0 -0
- pyrill-0.2.0/src/pyrill/queues.py +156 -0
- pyrill-0.2.0/src/pyrill/sinks.py +156 -0
- pyrill-0.2.0/src/pyrill/sources.py +237 -0
- pyrill-0.2.0/src/pyrill/strings.py +570 -0
- pyrill-0.2.0/src/pyrill/strlike.py +301 -0
- pyrill-0.2.0/src/pyrill/time.py +194 -0
pyrill-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pyrill
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Asynchronous streams for Python
|
|
5
|
+
Keywords: stream,flow,dataflow,reactive
|
|
6
|
+
Author: Alfred
|
|
7
|
+
Author-email: Alfred <alfred82santa@gmail.com>
|
|
8
|
+
License: LGPL-2.1-or-later
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Project-URL: Repository, https://github.com/pyrill-framework/pyrill
|
|
15
|
+
Project-URL: Homepage, https://github.com/pyrill-framework/pyrill
|
|
16
|
+
Project-URL: Documentation, https://pyrill-framework.readthedocs.io/stable/
|
|
17
|
+
Project-URL: Issues, https://github.com/pyrill-framework/pyrill/issues
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# PyRill
|
|
21
|
+
|
|
22
|
+
Asynchronous streams for Python.
|
|
23
|
+
|
|
24
|
+
PyRill builds asynchronous data pipelines in plain Python. You connect small
|
|
25
|
+
elements (sources, stages and sinks) with `>>`, and the data flows through them one
|
|
26
|
+
frame at a time, on top of `asyncio` and with no dependencies.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
|
|
31
|
+
from pyrill.accumulators import ListAcc
|
|
32
|
+
from pyrill.mappers import Map
|
|
33
|
+
from pyrill.sinks import Last
|
|
34
|
+
from pyrill.sources import IterableSource
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def main() -> None:
|
|
38
|
+
pipeline = (
|
|
39
|
+
IterableSource(source=["alpha", "beta", "gamma"])
|
|
40
|
+
>> Map(map_func=str.upper)
|
|
41
|
+
>> ListAcc()
|
|
42
|
+
>> Last()
|
|
43
|
+
)
|
|
44
|
+
print(await pipeline.get_frame())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
asyncio.run(main())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
['ALPHA', 'BETA', 'GAMMA']
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Why PyRill
|
|
55
|
+
|
|
56
|
+
Programs that handle streams (files, sockets, API responses, queues) tend to grow
|
|
57
|
+
hand-written loops that mix reading, parsing, filtering, timing and error handling.
|
|
58
|
+
PyRill turns each of those concerns into a small, typed, reusable element that you
|
|
59
|
+
can test alone and connect with the others.
|
|
60
|
+
|
|
61
|
+
- **Pull-based.** Work happens only when a frame is needed, so a slow consumer slows
|
|
62
|
+
the whole pipeline, and frames do not pile up in memory unless you add a buffer.
|
|
63
|
+
- **Streaming.** A pipeline works on one frame at a time, so memory use depends on
|
|
64
|
+
the size of the frames, not on the length of the stream.
|
|
65
|
+
- **Batteries included.** Elements for files and asyncio streams, byte and text
|
|
66
|
+
re-chunking, safe UTF-8 decoding, CSV, JSON, branching (`Tee`), pairing (`Zip`),
|
|
67
|
+
merging (`Union`), timeouts and deadlines, pausing (`Gate`), buffering (`Queue`)
|
|
68
|
+
and more.
|
|
69
|
+
- **Extensible.** Write your own sources, stages and sinks, or package a chain of
|
|
70
|
+
stages as a single reusable stage.
|
|
71
|
+
- **Typed.** Elements are generic classes, so type checkers follow the frames through
|
|
72
|
+
a pipeline.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
PyRill needs Python 3.14 or later and has no other dependencies.
|
|
77
|
+
|
|
78
|
+
```console
|
|
79
|
+
$ pip install pyrill
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
To work on PyRill itself, clone the repository, run `uv sync`, and run the tests with
|
|
83
|
+
`uv run pytest`.
|
|
84
|
+
|
|
85
|
+
## Documentation
|
|
86
|
+
|
|
87
|
+
Full documentation, including a quickstart, the core concepts, task-oriented guides
|
|
88
|
+
and a catalog of every element, is at
|
|
89
|
+
<https://pyrill-framework.readthedocs.io/stable/>. The same pages are in the
|
|
90
|
+
[`docs/`](docs/source) folder.
|
|
91
|
+
|
|
92
|
+
The [messages and monitoring guide](docs/source/guides/messages-and-monitoring.rst)
|
|
93
|
+
explains how to observe lifecycle changes, rejected frames and file activity, and
|
|
94
|
+
publish custom progress events through the pipeline's message bus.
|
|
95
|
+
|
|
96
|
+
## Project
|
|
97
|
+
|
|
98
|
+
- Repository and issue tracker: <https://github.com/pyrill-framework/pyrill>
|
|
99
|
+
- License: LGPL-2.1-or-later
|
pyrill-0.2.0/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# PyRill
|
|
2
|
+
|
|
3
|
+
Asynchronous streams for Python.
|
|
4
|
+
|
|
5
|
+
PyRill builds asynchronous data pipelines in plain Python. You connect small
|
|
6
|
+
elements (sources, stages and sinks) with `>>`, and the data flows through them one
|
|
7
|
+
frame at a time, on top of `asyncio` and with no dependencies.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import asyncio
|
|
11
|
+
|
|
12
|
+
from pyrill.accumulators import ListAcc
|
|
13
|
+
from pyrill.mappers import Map
|
|
14
|
+
from pyrill.sinks import Last
|
|
15
|
+
from pyrill.sources import IterableSource
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def main() -> None:
|
|
19
|
+
pipeline = (
|
|
20
|
+
IterableSource(source=["alpha", "beta", "gamma"])
|
|
21
|
+
>> Map(map_func=str.upper)
|
|
22
|
+
>> ListAcc()
|
|
23
|
+
>> Last()
|
|
24
|
+
)
|
|
25
|
+
print(await pipeline.get_frame())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
asyncio.run(main())
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
['ALPHA', 'BETA', 'GAMMA']
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Why PyRill
|
|
36
|
+
|
|
37
|
+
Programs that handle streams (files, sockets, API responses, queues) tend to grow
|
|
38
|
+
hand-written loops that mix reading, parsing, filtering, timing and error handling.
|
|
39
|
+
PyRill turns each of those concerns into a small, typed, reusable element that you
|
|
40
|
+
can test alone and connect with the others.
|
|
41
|
+
|
|
42
|
+
- **Pull-based.** Work happens only when a frame is needed, so a slow consumer slows
|
|
43
|
+
the whole pipeline, and frames do not pile up in memory unless you add a buffer.
|
|
44
|
+
- **Streaming.** A pipeline works on one frame at a time, so memory use depends on
|
|
45
|
+
the size of the frames, not on the length of the stream.
|
|
46
|
+
- **Batteries included.** Elements for files and asyncio streams, byte and text
|
|
47
|
+
re-chunking, safe UTF-8 decoding, CSV, JSON, branching (`Tee`), pairing (`Zip`),
|
|
48
|
+
merging (`Union`), timeouts and deadlines, pausing (`Gate`), buffering (`Queue`)
|
|
49
|
+
and more.
|
|
50
|
+
- **Extensible.** Write your own sources, stages and sinks, or package a chain of
|
|
51
|
+
stages as a single reusable stage.
|
|
52
|
+
- **Typed.** Elements are generic classes, so type checkers follow the frames through
|
|
53
|
+
a pipeline.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
PyRill needs Python 3.14 or later and has no other dependencies.
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ pip install pyrill
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To work on PyRill itself, clone the repository, run `uv sync`, and run the tests with
|
|
64
|
+
`uv run pytest`.
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
Full documentation, including a quickstart, the core concepts, task-oriented guides
|
|
69
|
+
and a catalog of every element, is at
|
|
70
|
+
<https://pyrill-framework.readthedocs.io/stable/>. The same pages are in the
|
|
71
|
+
[`docs/`](docs/source) folder.
|
|
72
|
+
|
|
73
|
+
The [messages and monitoring guide](docs/source/guides/messages-and-monitoring.rst)
|
|
74
|
+
explains how to observe lifecycle changes, rejected frames and file activity, and
|
|
75
|
+
publish custom progress events through the pipeline's message bus.
|
|
76
|
+
|
|
77
|
+
## Project
|
|
78
|
+
|
|
79
|
+
- Repository and issue tracker: <https://github.com/pyrill-framework/pyrill>
|
|
80
|
+
- License: LGPL-2.1-or-later
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrill"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Asynchronous streams for Python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = []
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 4 - Beta",
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3.14",
|
|
12
|
+
"Typing :: Typed",
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"stream",
|
|
16
|
+
"flow",
|
|
17
|
+
"dataflow",
|
|
18
|
+
"reactive",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[[project.authors]]
|
|
22
|
+
name = "Alfred"
|
|
23
|
+
email = "alfred82santa@gmail.com"
|
|
24
|
+
|
|
25
|
+
[project.license]
|
|
26
|
+
text = "LGPL-2.1-or-later"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Repository = "https://github.com/pyrill-framework/pyrill"
|
|
30
|
+
Homepage = "https://github.com/pyrill-framework/pyrill"
|
|
31
|
+
Documentation = "https://pyrill-framework.readthedocs.io/stable/"
|
|
32
|
+
Issues = "https://github.com/pyrill-framework/pyrill/issues"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["uv_build>=0.11.3,<0.12.0"]
|
|
36
|
+
build-backend = "uv_build"
|
|
37
|
+
|
|
38
|
+
[tool.pytest]
|
|
39
|
+
asyncio_mode = "auto"
|
|
40
|
+
|
|
41
|
+
[tool.coverage.run]
|
|
42
|
+
omit = [".venv/*"]
|
|
43
|
+
branch = true
|
|
44
|
+
relative_files = false
|
|
45
|
+
|
|
46
|
+
[tool.coverage.report]
|
|
47
|
+
exclude_also = [
|
|
48
|
+
"def __repr__",
|
|
49
|
+
'if self\.debug',
|
|
50
|
+
"raise AssertionError",
|
|
51
|
+
"raise NotImplementedError",
|
|
52
|
+
"if 0:",
|
|
53
|
+
"if __name__ == .__main__.:",
|
|
54
|
+
'@(abc\.)?abstractmethod',
|
|
55
|
+
"if TYPE_CHECKING",
|
|
56
|
+
"@overload",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.coverage.paths]
|
|
60
|
+
source = ["src/"]
|
|
61
|
+
|
|
62
|
+
[tool.poe]
|
|
63
|
+
envfile = [
|
|
64
|
+
".env",
|
|
65
|
+
".env.local",
|
|
66
|
+
]
|
|
67
|
+
executor = "uv"
|
|
68
|
+
|
|
69
|
+
[tool.poe.tasks]
|
|
70
|
+
build = "uv build"
|
|
71
|
+
test = "pytest -v -s --junitxml=pytest.xml --cov src --cov-report term-missing --cov-report xml:coverage.xml --cov-fail-under=85 --exitfirst"
|
|
72
|
+
typecheck = "mypy src"
|
|
73
|
+
format = "ruff format src/**/*.py tests/**/*.py"
|
|
74
|
+
"format:check" = "ruff format src/**/*.py tests/**/*.py --check"
|
|
75
|
+
lint = "ruff check src/**/*.py tests/**/*.py"
|
|
76
|
+
"lint:fix" = "ruff check src/**/*.py tests/**/*.py --fix"
|
|
77
|
+
|
|
78
|
+
[tool.poe.tasks.clean]
|
|
79
|
+
cmd = """
|
|
80
|
+
rm -rf ./**/src/**/*.pyc
|
|
81
|
+
./**/src/**/*.pyo
|
|
82
|
+
./**/src/**/__pycache__
|
|
83
|
+
./**/tests/**/*.pyc
|
|
84
|
+
./**/tests/**/*.pyo
|
|
85
|
+
./**/tests/**/__pycache__
|
|
86
|
+
.coverage
|
|
87
|
+
coverage.xml
|
|
88
|
+
pytest.xml
|
|
89
|
+
.*_cache
|
|
90
|
+
"""
|
|
91
|
+
empty_glob = "null"
|
|
92
|
+
|
|
93
|
+
[tool.poe.tasks.validate]
|
|
94
|
+
help = "Execute all validations: typecheck, lint, and test"
|
|
95
|
+
sequence = [
|
|
96
|
+
"typecheck",
|
|
97
|
+
"format:check",
|
|
98
|
+
"lint",
|
|
99
|
+
"test",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[tool.poe.tasks.prebuild]
|
|
103
|
+
help = "Pre-build checks: typecheck and lint"
|
|
104
|
+
sequence = [
|
|
105
|
+
"typecheck",
|
|
106
|
+
"lint",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[tool.poe.tasks."clean:all"]
|
|
110
|
+
help = "Complete cleanup: cache, coverage, and temporary files"
|
|
111
|
+
sequence = [
|
|
112
|
+
"clean",
|
|
113
|
+
{ cmd = """
|
|
114
|
+
rm -rf dist
|
|
115
|
+
build
|
|
116
|
+
.coverage
|
|
117
|
+
coverage.xml
|
|
118
|
+
pytest.xml
|
|
119
|
+
**/.*_cache
|
|
120
|
+
""" },
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[tool.poe.tasks.fix]
|
|
124
|
+
help = "Fix linting and formatting issues, then run tests"
|
|
125
|
+
sequence = [
|
|
126
|
+
"format",
|
|
127
|
+
"lint:fix",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
[tool.poe.tasks.docs]
|
|
131
|
+
help = "Build the documentation"
|
|
132
|
+
cmd = "sphinx-build -b html docs/source docs/build"
|
|
133
|
+
|
|
134
|
+
[tool.poe.tasks."docs:clean"]
|
|
135
|
+
help = "Clean the documentation build"
|
|
136
|
+
cmd = "rm -rf docs/build"
|
|
137
|
+
|
|
138
|
+
[dependency-groups]
|
|
139
|
+
dev = [
|
|
140
|
+
"mypy>=2.4.0",
|
|
141
|
+
"pytest>=9.1.1",
|
|
142
|
+
"pytest-asyncio>=1.4.0",
|
|
143
|
+
"pytest-cov>=7.1.0",
|
|
144
|
+
"ruff>=0.17.3",
|
|
145
|
+
]
|
|
146
|
+
docs = ["sphinx>=9.2.0"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrill"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Asynchronous streams for Python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Alfred", email = "alfred82santa@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
dependencies = []
|
|
9
|
+
license = { text = "LGPL-2.1-or-later" }
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.14",
|
|
14
|
+
"Typing :: Typed",
|
|
15
|
+
]
|
|
16
|
+
keywords = ["stream", "flow", "dataflow", "reactive"]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Repository = "https://github.com/pyrill-framework/pyrill"
|
|
20
|
+
Homepage = "https://github.com/pyrill-framework/pyrill"
|
|
21
|
+
Documentation = "https://pyrill-framework.readthedocs.io/stable/"
|
|
22
|
+
Issues = "https://github.com/pyrill-framework/pyrill/issues"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["uv_build>=0.11.3,<0.12.0"]
|
|
27
|
+
build-backend = "uv_build"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[tool.pytest]
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"mypy>=2.4.0",
|
|
36
|
+
"pytest>=9.1.1",
|
|
37
|
+
"pytest-asyncio>=1.4.0",
|
|
38
|
+
"pytest-cov>=7.1.0",
|
|
39
|
+
"ruff>=0.17.3",
|
|
40
|
+
]
|
|
41
|
+
docs = ["sphinx>=9.2.0"]
|
|
42
|
+
|
|
43
|
+
[tool.coverage.run]
|
|
44
|
+
omit = [".venv/*"]
|
|
45
|
+
branch = true
|
|
46
|
+
relative_files = false
|
|
47
|
+
|
|
48
|
+
[tool.coverage.report]
|
|
49
|
+
# Regexes for lines to exclude from consideration
|
|
50
|
+
exclude_also = [
|
|
51
|
+
# Dont complain about missing debug-only code:
|
|
52
|
+
"def __repr__",
|
|
53
|
+
"if self\\.debug",
|
|
54
|
+
|
|
55
|
+
# Don't complain if tests don't hit defensive assertion code:
|
|
56
|
+
"raise AssertionError",
|
|
57
|
+
"raise NotImplementedError",
|
|
58
|
+
|
|
59
|
+
# Don't complain if non-runnable code isn't run:
|
|
60
|
+
"if 0:",
|
|
61
|
+
"if __name__ == .__main__.:",
|
|
62
|
+
|
|
63
|
+
# Don't complain about abstract methods, they aren't run:
|
|
64
|
+
"@(abc\\.)?abstractmethod",
|
|
65
|
+
|
|
66
|
+
# Don't complain type checking imports, they aren't run:
|
|
67
|
+
"if TYPE_CHECKING",
|
|
68
|
+
|
|
69
|
+
# Don't complain overloads, they aren't run:
|
|
70
|
+
"@overload",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.coverage.paths]
|
|
74
|
+
source = ["src/"]
|
|
75
|
+
|
|
76
|
+
[tool.poe]
|
|
77
|
+
envfile = [".env", ".env.local"]
|
|
78
|
+
executor = "uv"
|
|
79
|
+
|
|
80
|
+
[tool.poe.tasks]
|
|
81
|
+
build = "uv build"
|
|
82
|
+
test = "pytest -v -s --junitxml=pytest.xml --cov src --cov-report term-missing --cov-report xml:coverage.xml --cov-fail-under=85 --exitfirst"
|
|
83
|
+
typecheck = "mypy src"
|
|
84
|
+
format = "ruff format src/**/*.py tests/**/*.py"
|
|
85
|
+
"format:check" = "ruff format src/**/*.py tests/**/*.py --check"
|
|
86
|
+
lint = "ruff check src/**/*.py tests/**/*.py"
|
|
87
|
+
"lint:fix" = "ruff check src/**/*.py tests/**/*.py --fix"
|
|
88
|
+
|
|
89
|
+
[tool.poe.tasks.clean]
|
|
90
|
+
cmd = """
|
|
91
|
+
rm -rf ./**/src/**/*.pyc
|
|
92
|
+
./**/src/**/*.pyo
|
|
93
|
+
./**/src/**/__pycache__
|
|
94
|
+
./**/tests/**/*.pyc
|
|
95
|
+
./**/tests/**/*.pyo
|
|
96
|
+
./**/tests/**/__pycache__
|
|
97
|
+
.coverage
|
|
98
|
+
coverage.xml
|
|
99
|
+
pytest.xml
|
|
100
|
+
.*_cache
|
|
101
|
+
"""
|
|
102
|
+
empty_glob = "null"
|
|
103
|
+
|
|
104
|
+
[tool.poe.tasks.validate]
|
|
105
|
+
help = "Execute all validations: typecheck, lint, and test"
|
|
106
|
+
sequence = ["typecheck", "format:check", "lint", "test"]
|
|
107
|
+
|
|
108
|
+
[tool.poe.tasks.prebuild]
|
|
109
|
+
help = "Pre-build checks: typecheck and lint"
|
|
110
|
+
sequence = ["typecheck", "lint"]
|
|
111
|
+
|
|
112
|
+
[tool.poe.tasks."clean:all"]
|
|
113
|
+
help = "Complete cleanup: cache, coverage, and temporary files"
|
|
114
|
+
sequence = ["clean", { cmd = """rm -rf dist
|
|
115
|
+
build
|
|
116
|
+
.coverage
|
|
117
|
+
coverage.xml
|
|
118
|
+
pytest.xml
|
|
119
|
+
**/.*_cache
|
|
120
|
+
""" }]
|
|
121
|
+
|
|
122
|
+
[tool.poe.tasks.fix]
|
|
123
|
+
help = "Fix linting and formatting issues, then run tests"
|
|
124
|
+
sequence = ["format", "lint:fix"]
|
|
125
|
+
|
|
126
|
+
[tool.poe.tasks.docs]
|
|
127
|
+
help = "Build the documentation"
|
|
128
|
+
cmd = "sphinx-build -b html docs/source docs/build"
|
|
129
|
+
|
|
130
|
+
[tool.poe.tasks."docs:clean"]
|
|
131
|
+
help = "Clean the documentation build"
|
|
132
|
+
cmd = "rm -rf docs/build"
|
|
File without changes
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from collections.abc import Sized
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from .base import BaseStage
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Count",
|
|
8
|
+
"ListAcc",
|
|
9
|
+
"Size",
|
|
10
|
+
"SumAcc",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SumAcc[AccData: (str, bytes, int, complex, float)](BaseStage[AccData, AccData]):
|
|
15
|
+
"""
|
|
16
|
+
Stage that emits the running sum of the frames received so far.
|
|
17
|
+
|
|
18
|
+
After every frame the stage emits the accumulated value, so a source of ``n``
|
|
19
|
+
frames produces ``n`` outputs. The sum uses ``+``, so it also concatenates ``str``
|
|
20
|
+
and ``bytes`` frames. The accumulator is reset to ``initial_value`` on every mount
|
|
21
|
+
and cleared on unmount.
|
|
22
|
+
|
|
23
|
+
:param initial_value: Value the sum starts from. When ``None`` (default), the first
|
|
24
|
+
frame becomes the initial value.
|
|
25
|
+
|
|
26
|
+
Input and output::
|
|
27
|
+
|
|
28
|
+
input: 1, 2, 3
|
|
29
|
+
output: 1, 3, 6
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
|
|
33
|
+
.. code-block:: python
|
|
34
|
+
|
|
35
|
+
from pyrill.accumulators import SumAcc
|
|
36
|
+
from pyrill.sources import IterableSource
|
|
37
|
+
|
|
38
|
+
source = IterableSource(source=[1, 2, 3])
|
|
39
|
+
stage = source >> SumAcc(initial_value=10)
|
|
40
|
+
|
|
41
|
+
await stage.mount()
|
|
42
|
+
frames = [frame async for frame in stage]
|
|
43
|
+
# [11, 13, 16]
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, *args: Any, initial_value: AccData | None = None, **kwargs: Any):
|
|
47
|
+
super().__init__(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
self.initial_value: AccData | None = initial_value
|
|
50
|
+
|
|
51
|
+
self._accum: AccData | None = None
|
|
52
|
+
|
|
53
|
+
async def _mount(self) -> None:
|
|
54
|
+
self._accum = self.initial_value
|
|
55
|
+
await super()._mount()
|
|
56
|
+
|
|
57
|
+
async def _unmount(self) -> None:
|
|
58
|
+
self._accum = None
|
|
59
|
+
await super()._unmount()
|
|
60
|
+
|
|
61
|
+
async def _process_frame(self, frame: AccData) -> AccData:
|
|
62
|
+
if self._accum is None:
|
|
63
|
+
self._accum = frame
|
|
64
|
+
else:
|
|
65
|
+
self._accum += frame
|
|
66
|
+
return self._accum
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ListAcc[Source_co](BaseStage[Source_co, list[Source_co]]):
|
|
70
|
+
"""
|
|
71
|
+
Stage that emits the list of all the frames received so far.
|
|
72
|
+
|
|
73
|
+
After every frame the stage emits a new list (a copy, so earlier outputs are not
|
|
74
|
+
modified later) holding every frame seen since the stage was mounted. The list is
|
|
75
|
+
reset on every mount.
|
|
76
|
+
|
|
77
|
+
Input and output::
|
|
78
|
+
|
|
79
|
+
input: "a", "b", "c"
|
|
80
|
+
output: ["a"], ["a", "b"], ["a", "b", "c"]
|
|
81
|
+
|
|
82
|
+
Usage:
|
|
83
|
+
|
|
84
|
+
.. code-block:: python
|
|
85
|
+
|
|
86
|
+
from pyrill.accumulators import ListAcc
|
|
87
|
+
from pyrill.sources import IterableSource
|
|
88
|
+
|
|
89
|
+
source = IterableSource(source=["a", "b", "c"])
|
|
90
|
+
stage = source >> ListAcc()
|
|
91
|
+
|
|
92
|
+
await stage.mount()
|
|
93
|
+
frames = [frame async for frame in stage]
|
|
94
|
+
# [["a"], ["a", "b"], ["a", "b", "c"]]
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
98
|
+
super().__init__(*args, **kwargs)
|
|
99
|
+
|
|
100
|
+
self._accum: list[Source_co] = []
|
|
101
|
+
|
|
102
|
+
async def _mount(self) -> None:
|
|
103
|
+
self._accum = []
|
|
104
|
+
await super()._mount()
|
|
105
|
+
|
|
106
|
+
async def _process_frame(self, frame: Source_co) -> list[Source_co]:
|
|
107
|
+
self._accum.append(frame)
|
|
108
|
+
|
|
109
|
+
return self._accum.copy()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Count(BaseStage[Any, int]):
|
|
113
|
+
"""
|
|
114
|
+
Stage that emits how many frames it has received so far.
|
|
115
|
+
|
|
116
|
+
The frame content is ignored. After every frame the stage emits the number of frames
|
|
117
|
+
seen since it was mounted, starting at ``1``. The counter is reset on mount and on
|
|
118
|
+
unmount.
|
|
119
|
+
|
|
120
|
+
Input and output::
|
|
121
|
+
|
|
122
|
+
input: "a", "b", "c"
|
|
123
|
+
output: 1, 2, 3
|
|
124
|
+
|
|
125
|
+
Usage:
|
|
126
|
+
|
|
127
|
+
.. code-block:: python
|
|
128
|
+
|
|
129
|
+
from pyrill.accumulators import Count
|
|
130
|
+
from pyrill.sources import IterableSource
|
|
131
|
+
|
|
132
|
+
source = IterableSource(source=["a", "b", "c"])
|
|
133
|
+
stage = source >> Count()
|
|
134
|
+
|
|
135
|
+
await stage.mount()
|
|
136
|
+
frames = [frame async for frame in stage]
|
|
137
|
+
# [1, 2, 3]
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
_counter: int = 0
|
|
141
|
+
|
|
142
|
+
async def _mount(self) -> None:
|
|
143
|
+
self._counter = 0
|
|
144
|
+
await super()._mount()
|
|
145
|
+
|
|
146
|
+
async def _unmount(self) -> None:
|
|
147
|
+
self._counter = 0
|
|
148
|
+
await super()._unmount()
|
|
149
|
+
|
|
150
|
+
async def _process_frame(self, frame: Any) -> int:
|
|
151
|
+
self._counter += 1
|
|
152
|
+
return self._counter
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Size(BaseStage[Sized, int]):
|
|
156
|
+
"""
|
|
157
|
+
Stage that emits the total ``len`` of the frames received so far.
|
|
158
|
+
|
|
159
|
+
Frames can be any sized object (``str``, ``bytes``, ``list``...). After every frame
|
|
160
|
+
the stage emits the sum of the lengths seen since it was mounted. The total is reset
|
|
161
|
+
on mount and on unmount.
|
|
162
|
+
|
|
163
|
+
Input and output::
|
|
164
|
+
|
|
165
|
+
input: "ab", "cde", "f"
|
|
166
|
+
output: 2, 5, 6
|
|
167
|
+
|
|
168
|
+
Usage:
|
|
169
|
+
|
|
170
|
+
.. code-block:: python
|
|
171
|
+
|
|
172
|
+
from pyrill.accumulators import Size
|
|
173
|
+
from pyrill.sources import IterableSource
|
|
174
|
+
|
|
175
|
+
source = IterableSource(source=["ab", "cde", "f"])
|
|
176
|
+
stage = source >> Size()
|
|
177
|
+
|
|
178
|
+
await stage.mount()
|
|
179
|
+
frames = [frame async for frame in stage]
|
|
180
|
+
# [2, 5, 6]
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
_acc: int = 0
|
|
184
|
+
|
|
185
|
+
async def _mount(self) -> None:
|
|
186
|
+
self._acc = 0
|
|
187
|
+
await super()._mount()
|
|
188
|
+
|
|
189
|
+
async def _unmount(self) -> None:
|
|
190
|
+
self._acc = 0
|
|
191
|
+
await super()._unmount()
|
|
192
|
+
|
|
193
|
+
async def _process_frame(self, frame: Sized) -> int:
|
|
194
|
+
self._acc += len(frame)
|
|
195
|
+
return self._acc
|