pineapple-pine 0.7.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.
- pineapple_pine-0.7.0/PKG-INFO +78 -0
- pineapple_pine-0.7.0/README.md +51 -0
- pineapple_pine-0.7.0/pine/__init__.py +8 -0
- pineapple_pine-0.7.0/pine/cancellation.py +20 -0
- pineapple_pine-0.7.0/pine/cli/__init__.py +0 -0
- pineapple_pine-0.7.0/pine/cli/codegen.py +67 -0
- pineapple_pine-0.7.0/pine/cli/dag.py +62 -0
- pineapple_pine-0.7.0/pine/cli/run.py +97 -0
- pineapple_pine-0.7.0/pine/cli/server.py +346 -0
- pineapple_pine-0.7.0/pine/config.py +304 -0
- pineapple_pine-0.7.0/pine/dag.py +218 -0
- pineapple_pine-0.7.0/pine/engine.py +681 -0
- pineapple_pine-0.7.0/pine/errors.py +31 -0
- pineapple_pine-0.7.0/pine/frame.py +237 -0
- pineapple_pine-0.7.0/pine/go_format.py +181 -0
- pineapple_pine-0.7.0/pine/operator.py +346 -0
- pineapple_pine-0.7.0/pine/operators/__init__.py +412 -0
- pineapple_pine-0.7.0/pine/operators/filter_condition.py +29 -0
- pineapple_pine-0.7.0/pine/operators/filter_paginate.py +48 -0
- pineapple_pine-0.7.0/pine/operators/filter_truncate.py +35 -0
- pineapple_pine-0.7.0/pine/operators/merge_dedup.py +38 -0
- pineapple_pine-0.7.0/pine/operators/observe_log.py +53 -0
- pineapple_pine-0.7.0/pine/operators/recall_resource.py +53 -0
- pineapple_pine-0.7.0/pine/operators/recall_static.py +38 -0
- pineapple_pine-0.7.0/pine/operators/reorder_shuffle.py +92 -0
- pineapple_pine-0.7.0/pine/operators/reorder_sort.py +62 -0
- pineapple_pine-0.7.0/pine/operators/transform_by_lua.py +308 -0
- pineapple_pine-0.7.0/pine/operators/transform_copy.py +58 -0
- pineapple_pine-0.7.0/pine/operators/transform_dispatch.py +29 -0
- pineapple_pine-0.7.0/pine/operators/transform_normalize.py +59 -0
- pineapple_pine-0.7.0/pine/operators/transform_redis_get.py +138 -0
- pineapple_pine-0.7.0/pine/operators/transform_redis_set.py +147 -0
- pineapple_pine-0.7.0/pine/operators/transform_remote_pineapple.py +224 -0
- pineapple_pine-0.7.0/pine/operators/transform_resource_lookup.py +87 -0
- pineapple_pine-0.7.0/pine/operators/transform_size.py +25 -0
- pineapple_pine-0.7.0/pine/parallel.py +88 -0
- pineapple_pine-0.7.0/pine/py.typed +0 -0
- pineapple_pine-0.7.0/pine/registry.py +88 -0
- pineapple_pine-0.7.0/pine/result.py +32 -0
- pineapple_pine-0.7.0/pine/stats.py +98 -0
- pineapple_pine-0.7.0/pine/visualize.py +184 -0
- pineapple_pine-0.7.0/pineapple_pine.egg-info/PKG-INFO +78 -0
- pineapple_pine-0.7.0/pineapple_pine.egg-info/SOURCES.txt +50 -0
- pineapple_pine-0.7.0/pineapple_pine.egg-info/dependency_links.txt +1 -0
- pineapple_pine-0.7.0/pineapple_pine.egg-info/requires.txt +9 -0
- pineapple_pine-0.7.0/pineapple_pine.egg-info/top_level.txt +1 -0
- pineapple_pine-0.7.0/pyproject.toml +50 -0
- pineapple_pine-0.7.0/setup.cfg +4 -0
- pineapple_pine-0.7.0/tests/test_bench.py +274 -0
- pineapple_pine-0.7.0/tests/test_fixtures.py +300 -0
- pineapple_pine-0.7.0/tests/test_fuzz.py +228 -0
- pineapple_pine-0.7.0/tests/test_lua_pool.py +88 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pineapple-pine
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Pineapple pipeline engine — Python runtime
|
|
5
|
+
Author-email: Liam Huang <liam0205@hotmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/Liam0205/pineapple
|
|
8
|
+
Project-URL: Repository, https://github.com/Liam0205/pineapple
|
|
9
|
+
Project-URL: Issues, https://github.com/Liam0205/pineapple/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: lupa>=2.0
|
|
20
|
+
Requires-Dist: redis>=5.0
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "dev"
|
|
25
|
+
Requires-Dist: hypothesis>=6.100; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# pineapple-pine
|
|
29
|
+
|
|
30
|
+
Python runtime engine for the [Pineapple](https://github.com/Liam0205/pineapple) pipeline framework.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install pineapple-pine
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import json
|
|
42
|
+
from pine.engine import Engine
|
|
43
|
+
|
|
44
|
+
config = json.dumps({
|
|
45
|
+
"operators": [
|
|
46
|
+
{"type_name": "recall_static", "name": "recall", "$metadata": {
|
|
47
|
+
"common_input": [], "item_input": [], "common_output": [],
|
|
48
|
+
"item_output": ["id", "score"]
|
|
49
|
+
}, "items": [{"id": "a", "score": 1.0}]}
|
|
50
|
+
],
|
|
51
|
+
"pipeline_groups": {"main": ["recall"]}
|
|
52
|
+
}).encode()
|
|
53
|
+
|
|
54
|
+
engine = Engine.create(config)
|
|
55
|
+
result = engine.execute(common={}, items=[])
|
|
56
|
+
print(result.common, result.items)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
- Full DAG scheduling with data-hazard analysis
|
|
62
|
+
- ConsumesRowSet / MutatesRowSet / AdditiveWritesRowSet marker interfaces
|
|
63
|
+
- Framework-level field accessor (Strict / Default semantics)
|
|
64
|
+
- Lua operator support via `lupa`
|
|
65
|
+
- HTTP server with hot-reload
|
|
66
|
+
- DAG visualization (DOT / Mermaid)
|
|
67
|
+
- Cross-engine parity with pine-go and pine-java
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- Python >= 3.11
|
|
72
|
+
- `lupa` (Lua runtime)
|
|
73
|
+
- `redis` (optional, for redis_get/redis_set operators)
|
|
74
|
+
- `httpx` (for remote_pineapple operator)
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
Apache-2.0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# pineapple-pine
|
|
2
|
+
|
|
3
|
+
Python runtime engine for the [Pineapple](https://github.com/Liam0205/pineapple) pipeline framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pineapple-pine
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import json
|
|
15
|
+
from pine.engine import Engine
|
|
16
|
+
|
|
17
|
+
config = json.dumps({
|
|
18
|
+
"operators": [
|
|
19
|
+
{"type_name": "recall_static", "name": "recall", "$metadata": {
|
|
20
|
+
"common_input": [], "item_input": [], "common_output": [],
|
|
21
|
+
"item_output": ["id", "score"]
|
|
22
|
+
}, "items": [{"id": "a", "score": 1.0}]}
|
|
23
|
+
],
|
|
24
|
+
"pipeline_groups": {"main": ["recall"]}
|
|
25
|
+
}).encode()
|
|
26
|
+
|
|
27
|
+
engine = Engine.create(config)
|
|
28
|
+
result = engine.execute(common={}, items=[])
|
|
29
|
+
print(result.common, result.items)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- Full DAG scheduling with data-hazard analysis
|
|
35
|
+
- ConsumesRowSet / MutatesRowSet / AdditiveWritesRowSet marker interfaces
|
|
36
|
+
- Framework-level field accessor (Strict / Default semantics)
|
|
37
|
+
- Lua operator support via `lupa`
|
|
38
|
+
- HTTP server with hot-reload
|
|
39
|
+
- DAG visualization (DOT / Mermaid)
|
|
40
|
+
- Cross-engine parity with pine-go and pine-java
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Python >= 3.11
|
|
45
|
+
- `lupa` (Lua runtime)
|
|
46
|
+
- `redis` (optional, for redis_get/redis_set operators)
|
|
47
|
+
- `httpx` (for remote_pineapple operator)
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
Apache-2.0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from pine.engine import Engine
|
|
2
|
+
from pine.errors import ConfigError, OperatorException, PanicError, ValidationError
|
|
3
|
+
from pine.registry import Registry
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Engine", "Registry", "ConfigError", "OperatorException",
|
|
7
|
+
"PanicError", "ValidationError",
|
|
8
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CancellationToken:
|
|
5
|
+
def __init__(self, parent: "CancellationToken | None" = None):
|
|
6
|
+
self._cancelled = threading.Event()
|
|
7
|
+
self._parent = parent
|
|
8
|
+
|
|
9
|
+
def cancel(self):
|
|
10
|
+
self._cancelled.set()
|
|
11
|
+
|
|
12
|
+
def is_cancelled(self) -> bool:
|
|
13
|
+
if self._cancelled.is_set():
|
|
14
|
+
return True
|
|
15
|
+
if self._parent is not None:
|
|
16
|
+
return self._parent.is_cancelled()
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
def child(self) -> "CancellationToken":
|
|
20
|
+
return CancellationToken(parent=self)
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pine.registry import Registry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
from pine.operators import ensure_registered
|
|
13
|
+
ensure_registered()
|
|
14
|
+
|
|
15
|
+
output_dir = ""
|
|
16
|
+
schema_json_path = ""
|
|
17
|
+
export_schema = ""
|
|
18
|
+
args = sys.argv[1:]
|
|
19
|
+
i = 0
|
|
20
|
+
while i < len(args):
|
|
21
|
+
if args[i] == "-output" and i + 1 < len(args):
|
|
22
|
+
i += 1
|
|
23
|
+
output_dir = args[i]
|
|
24
|
+
elif args[i] == "-schema-json" and i + 1 < len(args):
|
|
25
|
+
i += 1
|
|
26
|
+
schema_json_path = args[i]
|
|
27
|
+
elif args[i] == "--export-schema" and i + 1 < len(args):
|
|
28
|
+
i += 1
|
|
29
|
+
export_schema = args[i]
|
|
30
|
+
i += 1
|
|
31
|
+
|
|
32
|
+
if export_schema or schema_json_path:
|
|
33
|
+
out_path = export_schema or schema_json_path
|
|
34
|
+
schemas = Registry.global_instance().schemas()
|
|
35
|
+
schema_list: list[dict[str, Any]] = []
|
|
36
|
+
for schema in schemas:
|
|
37
|
+
params: dict[str, Any] = {}
|
|
38
|
+
for pname, pspec in schema.params.items():
|
|
39
|
+
params[pname] = {
|
|
40
|
+
"Type": pspec.type,
|
|
41
|
+
"Required": pspec.required,
|
|
42
|
+
"Default": pspec.default_value,
|
|
43
|
+
"Description": pspec.description,
|
|
44
|
+
}
|
|
45
|
+
schema_list.append({
|
|
46
|
+
"Name": schema.name,
|
|
47
|
+
"Type": schema.type.value,
|
|
48
|
+
"Description": schema.description,
|
|
49
|
+
"Params": params,
|
|
50
|
+
})
|
|
51
|
+
Path(out_path).write_text(
|
|
52
|
+
json.dumps(schema_list, indent=2, ensure_ascii=False)
|
|
53
|
+
)
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
if not output_dir:
|
|
57
|
+
print("Usage: Codegen --export-schema <path> | -schema-json <path> | -output <dir>",
|
|
58
|
+
file=sys.stderr)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
# TODO: codegen Python output (generate operators.py, resources.py, __init__.py)
|
|
62
|
+
print(f"codegen output to {output_dir} not yet implemented", file=sys.stderr)
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
from pine.operators import ensure_registered
|
|
9
|
+
ensure_registered()
|
|
10
|
+
|
|
11
|
+
config_path = ""
|
|
12
|
+
format_ = "dot"
|
|
13
|
+
collapse = 0
|
|
14
|
+
|
|
15
|
+
args = sys.argv[1:]
|
|
16
|
+
i = 0
|
|
17
|
+
while i < len(args):
|
|
18
|
+
if args[i] == "-config" and i + 1 < len(args):
|
|
19
|
+
i += 1
|
|
20
|
+
config_path = args[i]
|
|
21
|
+
elif args[i] == "-format" and i + 1 < len(args):
|
|
22
|
+
i += 1
|
|
23
|
+
format_ = args[i]
|
|
24
|
+
elif args[i] == "-collapse" and i + 1 < len(args):
|
|
25
|
+
i += 1
|
|
26
|
+
collapse = int(args[i])
|
|
27
|
+
i += 1
|
|
28
|
+
|
|
29
|
+
if not config_path:
|
|
30
|
+
print(
|
|
31
|
+
"Usage: RenderDAGCli -config <path> [-format dot|mermaid] [-collapse N]",
|
|
32
|
+
file=sys.stderr,
|
|
33
|
+
)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
data = Path(config_path).read_bytes()
|
|
38
|
+
except IOError as e:
|
|
39
|
+
print(f"error reading config: {e}", file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
from pine.engine import Engine, StaticResourceProvider
|
|
43
|
+
from pine.errors import ConfigError, RegistryError
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
rp = StaticResourceProvider({})
|
|
47
|
+
engine = Engine.create(data, resource_provider=rp)
|
|
48
|
+
except (ConfigError, RegistryError) as e:
|
|
49
|
+
print(f"error creating engine: {e}", file=sys.stderr)
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
output = engine.render_dag(format_, collapse)
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
print(f"error rendering DAG: {e}", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
sys.stdout.write(output)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pine.go_format import go_json_marshal_indent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
from pine.operators import ensure_registered
|
|
13
|
+
ensure_registered()
|
|
14
|
+
|
|
15
|
+
config_path = ""
|
|
16
|
+
request_path = ""
|
|
17
|
+
resources_path = ""
|
|
18
|
+
|
|
19
|
+
args = sys.argv[1:]
|
|
20
|
+
i = 0
|
|
21
|
+
while i < len(args):
|
|
22
|
+
if args[i] == "-config" and i + 1 < len(args):
|
|
23
|
+
i += 1
|
|
24
|
+
config_path = args[i]
|
|
25
|
+
elif args[i] == "-request" and i + 1 < len(args):
|
|
26
|
+
i += 1
|
|
27
|
+
request_path = args[i]
|
|
28
|
+
elif args[i] == "-static-resources" and i + 1 < len(args):
|
|
29
|
+
i += 1
|
|
30
|
+
resources_path = args[i]
|
|
31
|
+
i += 1
|
|
32
|
+
|
|
33
|
+
if not config_path or not request_path:
|
|
34
|
+
print(
|
|
35
|
+
"Usage: RunCli -config <pipeline.json> -request <request.json> "
|
|
36
|
+
"[-static-resources <resources.json>]",
|
|
37
|
+
file=sys.stderr,
|
|
38
|
+
)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
config_data = Path(config_path).read_bytes()
|
|
43
|
+
except IOError as e:
|
|
44
|
+
print(f"error reading config: {e}", file=sys.stderr)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
request_data = Path(request_path).read_bytes()
|
|
49
|
+
except IOError as e:
|
|
50
|
+
print(f"error reading request: {e}", file=sys.stderr)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
resource_provider = None
|
|
54
|
+
if resources_path:
|
|
55
|
+
try:
|
|
56
|
+
res_data = Path(resources_path).read_bytes()
|
|
57
|
+
resources = json.loads(res_data)
|
|
58
|
+
from pine.engine import StaticResourceProvider
|
|
59
|
+
resource_provider = StaticResourceProvider(resources)
|
|
60
|
+
except IOError as e:
|
|
61
|
+
print(f"error reading static resources: {e}", file=sys.stderr)
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
from pine.engine import Engine
|
|
65
|
+
from pine.errors import ConfigError, RegistryError
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
engine = Engine.create(config_data, resource_provider=resource_provider)
|
|
69
|
+
except (ConfigError, RegistryError) as e:
|
|
70
|
+
print(f"error creating engine: {e}", file=sys.stderr)
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
req = json.loads(request_data)
|
|
75
|
+
except json.JSONDecodeError as e:
|
|
76
|
+
print(f"error parsing request: {e}", file=sys.stderr)
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
|
|
79
|
+
common = req.get("common", {})
|
|
80
|
+
items = req.get("items", [])
|
|
81
|
+
|
|
82
|
+
result = engine.execute(common, items)
|
|
83
|
+
|
|
84
|
+
if result.error is not None:
|
|
85
|
+
print(f"execution error: {result.error}", file=sys.stderr)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
output: dict[str, Any] = {}
|
|
89
|
+
output["common"] = result.common
|
|
90
|
+
output["items"] = result.items
|
|
91
|
+
|
|
92
|
+
json_str = go_json_marshal_indent(output)
|
|
93
|
+
print(json_str)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
main()
|