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.
Files changed (52) hide show
  1. pineapple_pine-0.7.0/PKG-INFO +78 -0
  2. pineapple_pine-0.7.0/README.md +51 -0
  3. pineapple_pine-0.7.0/pine/__init__.py +8 -0
  4. pineapple_pine-0.7.0/pine/cancellation.py +20 -0
  5. pineapple_pine-0.7.0/pine/cli/__init__.py +0 -0
  6. pineapple_pine-0.7.0/pine/cli/codegen.py +67 -0
  7. pineapple_pine-0.7.0/pine/cli/dag.py +62 -0
  8. pineapple_pine-0.7.0/pine/cli/run.py +97 -0
  9. pineapple_pine-0.7.0/pine/cli/server.py +346 -0
  10. pineapple_pine-0.7.0/pine/config.py +304 -0
  11. pineapple_pine-0.7.0/pine/dag.py +218 -0
  12. pineapple_pine-0.7.0/pine/engine.py +681 -0
  13. pineapple_pine-0.7.0/pine/errors.py +31 -0
  14. pineapple_pine-0.7.0/pine/frame.py +237 -0
  15. pineapple_pine-0.7.0/pine/go_format.py +181 -0
  16. pineapple_pine-0.7.0/pine/operator.py +346 -0
  17. pineapple_pine-0.7.0/pine/operators/__init__.py +412 -0
  18. pineapple_pine-0.7.0/pine/operators/filter_condition.py +29 -0
  19. pineapple_pine-0.7.0/pine/operators/filter_paginate.py +48 -0
  20. pineapple_pine-0.7.0/pine/operators/filter_truncate.py +35 -0
  21. pineapple_pine-0.7.0/pine/operators/merge_dedup.py +38 -0
  22. pineapple_pine-0.7.0/pine/operators/observe_log.py +53 -0
  23. pineapple_pine-0.7.0/pine/operators/recall_resource.py +53 -0
  24. pineapple_pine-0.7.0/pine/operators/recall_static.py +38 -0
  25. pineapple_pine-0.7.0/pine/operators/reorder_shuffle.py +92 -0
  26. pineapple_pine-0.7.0/pine/operators/reorder_sort.py +62 -0
  27. pineapple_pine-0.7.0/pine/operators/transform_by_lua.py +308 -0
  28. pineapple_pine-0.7.0/pine/operators/transform_copy.py +58 -0
  29. pineapple_pine-0.7.0/pine/operators/transform_dispatch.py +29 -0
  30. pineapple_pine-0.7.0/pine/operators/transform_normalize.py +59 -0
  31. pineapple_pine-0.7.0/pine/operators/transform_redis_get.py +138 -0
  32. pineapple_pine-0.7.0/pine/operators/transform_redis_set.py +147 -0
  33. pineapple_pine-0.7.0/pine/operators/transform_remote_pineapple.py +224 -0
  34. pineapple_pine-0.7.0/pine/operators/transform_resource_lookup.py +87 -0
  35. pineapple_pine-0.7.0/pine/operators/transform_size.py +25 -0
  36. pineapple_pine-0.7.0/pine/parallel.py +88 -0
  37. pineapple_pine-0.7.0/pine/py.typed +0 -0
  38. pineapple_pine-0.7.0/pine/registry.py +88 -0
  39. pineapple_pine-0.7.0/pine/result.py +32 -0
  40. pineapple_pine-0.7.0/pine/stats.py +98 -0
  41. pineapple_pine-0.7.0/pine/visualize.py +184 -0
  42. pineapple_pine-0.7.0/pineapple_pine.egg-info/PKG-INFO +78 -0
  43. pineapple_pine-0.7.0/pineapple_pine.egg-info/SOURCES.txt +50 -0
  44. pineapple_pine-0.7.0/pineapple_pine.egg-info/dependency_links.txt +1 -0
  45. pineapple_pine-0.7.0/pineapple_pine.egg-info/requires.txt +9 -0
  46. pineapple_pine-0.7.0/pineapple_pine.egg-info/top_level.txt +1 -0
  47. pineapple_pine-0.7.0/pyproject.toml +50 -0
  48. pineapple_pine-0.7.0/setup.cfg +4 -0
  49. pineapple_pine-0.7.0/tests/test_bench.py +274 -0
  50. pineapple_pine-0.7.0/tests/test_fixtures.py +300 -0
  51. pineapple_pine-0.7.0/tests/test_fuzz.py +228 -0
  52. 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()