TalkPipe 0.4.2__tar.gz → 0.5.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 (113) hide show
  1. {talkpipe-0.4.2 → talkpipe-0.5.0}/CHANGELOG.md +29 -2
  2. {talkpipe-0.4.2 → talkpipe-0.5.0}/PKG-INFO +4 -2
  3. {talkpipe-0.4.2 → talkpipe-0.5.0}/pyproject.toml +9 -4
  4. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/__init__.py +2 -0
  5. talkpipe-0.5.0/src/talkpipe/app/__init__.py +0 -0
  6. talkpipe-0.5.0/src/talkpipe/app/apiendpoint.py +407 -0
  7. talkpipe-0.4.2/src/talkpipe/app/scriptendpoint.py → talkpipe-0.5.0/src/talkpipe/app/chatterlang_server.py +1 -1
  8. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/static/unit-docs.html +186 -6
  9. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/static/unit-docs.txt +141 -4
  10. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/chatterlang/parsers.py +5 -3
  11. talkpipe-0.5.0/src/talkpipe/data/email.py +457 -0
  12. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/data/html.py +15 -5
  13. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/data/mongo.py +20 -10
  14. talkpipe-0.5.0/src/talkpipe/operations/__init__.py +1 -0
  15. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/operations/matrices.py +5 -5
  16. talkpipe-0.5.0/src/talkpipe/operations/signatures.py +452 -0
  17. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/basic.py +110 -1
  18. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/util/data_manipulation.py +61 -2
  19. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe.egg-info/PKG-INFO +4 -2
  20. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe.egg-info/SOURCES.txt +7 -3
  21. talkpipe-0.5.0/src/talkpipe.egg-info/entry_points.txt +6 -0
  22. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe.egg-info/requires.txt +2 -0
  23. talkpipe-0.4.2/tests/talkpipe/chatterlang/test_scriptendpoint.py → talkpipe-0.5.0/tests/talkpipe/chatterlang/test_chatterlang_server.py +5 -5
  24. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/chatterlang/test_compiler.py +14 -1
  25. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/chatterlang/test_parsers.py +20 -0
  26. talkpipe-0.5.0/tests/talkpipe/data/test_email.py +265 -0
  27. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/operations/test_matrices.py +4 -4
  28. talkpipe-0.5.0/tests/talkpipe/operations/test_signatures.py +652 -0
  29. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/operations/test_threading.py +1 -1
  30. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/pipe/test_basic.py +43 -0
  31. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/test_documentation_examples.py +1 -1
  32. talkpipe-0.4.2/src/talkpipe/data/email.py +0 -184
  33. talkpipe-0.4.2/src/talkpipe/operations/__init__.py +0 -46
  34. talkpipe-0.4.2/src/talkpipe.egg-info/entry_points.txt +0 -3
  35. talkpipe-0.4.2/tests/talkpipe/data/test_email.py +0 -110
  36. {talkpipe-0.4.2 → talkpipe-0.5.0}/.github/ISSUE_TEMPLATE/release-checklist.md +0 -0
  37. {talkpipe-0.4.2 → talkpipe-0.5.0}/.gitignore +0 -0
  38. {talkpipe-0.4.2 → talkpipe-0.5.0}/.vscode/settings.json +0 -0
  39. {talkpipe-0.4.2 → talkpipe-0.5.0}/.vscode/tasks.json +0 -0
  40. {talkpipe-0.4.2 → talkpipe-0.5.0}/COPYRIGHT +0 -0
  41. {talkpipe-0.4.2 → talkpipe-0.5.0}/Dockerfile +0 -0
  42. {talkpipe-0.4.2 → talkpipe-0.5.0}/LICENSE +0 -0
  43. {talkpipe-0.4.2 → talkpipe-0.5.0}/README.md +0 -0
  44. {talkpipe-0.4.2 → talkpipe-0.5.0}/base-image/Dockerfile +0 -0
  45. {talkpipe-0.4.2 → talkpipe-0.5.0}/bin/config-analyzer.py +0 -0
  46. {talkpipe-0.4.2 → talkpipe-0.5.0}/bin/config_bash +0 -0
  47. {talkpipe-0.4.2 → talkpipe-0.5.0}/docs/LLMChat.png +0 -0
  48. {talkpipe-0.4.2 → talkpipe-0.5.0}/docs/LLMEvaluateWebPage.png +0 -0
  49. {talkpipe-0.4.2 → talkpipe-0.5.0}/docs/TalkPipe.png +0 -0
  50. {talkpipe-0.4.2 → talkpipe-0.5.0}/docs/TalkPipe.xcf +0 -0
  51. {talkpipe-0.4.2 → talkpipe-0.5.0}/pytest.ini +0 -0
  52. {talkpipe-0.4.2 → talkpipe-0.5.0}/setup.cfg +0 -0
  53. {talkpipe-0.4.2 → talkpipe-0.5.0}/setup.py +0 -0
  54. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/chatcli.py +0 -0
  55. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/runscript.py +0 -0
  56. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/static/.gitignore +0 -0
  57. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/static/favicon.ico +0 -0
  58. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/app/unit_documentation_analyzer.py +0 -0
  59. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/chatterlang/__init__.py +0 -0
  60. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/chatterlang/compiler.py +0 -0
  61. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/chatterlang/registry.py +0 -0
  62. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/data/__init__.py +0 -0
  63. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/data/extraction.py +0 -0
  64. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/data/rss.py +0 -0
  65. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/jupyter/basic.py +0 -0
  66. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/jupyter/examples.py +0 -0
  67. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/jupyter/interactive.py +0 -0
  68. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/__init__.py +0 -0
  69. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/chat.py +0 -0
  70. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/config.py +0 -0
  71. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/embedding.py +0 -0
  72. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/embedding_adapters.py +0 -0
  73. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/llm/prompt_adapters.py +0 -0
  74. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/operations/filtering.py +0 -0
  75. /talkpipe-0.4.2/src/talkpipe/operations/threading.py → /talkpipe-0.5.0/src/talkpipe/operations/thread_ops.py +0 -0
  76. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/operations/transforms.py +0 -0
  77. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/__init__.py +0 -0
  78. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/core.py +0 -0
  79. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/fork.py +0 -0
  80. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/io.py +0 -0
  81. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/pipe/math.py +0 -0
  82. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/util/__init__.py +0 -0
  83. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/util/collections.py +0 -0
  84. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/util/config.py +0 -0
  85. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe/util/os.py +0 -0
  86. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe.egg-info/dependency_links.txt +0 -0
  87. {talkpipe-0.4.2 → talkpipe-0.5.0}/src/talkpipe.egg-info/top_level.txt +0 -0
  88. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/conftest.py +0 -0
  89. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/app/test_chatcli.py +0 -0
  90. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/app/test_runscript.py +0 -0
  91. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/sample_feed.pkl +0 -0
  92. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test.docx +0 -0
  93. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_extraction.py +0 -0
  94. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_html.py +0 -0
  95. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_html_exceptions.py +0 -0
  96. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_mogomock.py +0 -0
  97. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_mongo.py +0 -0
  98. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_mongo_integration.py +0 -0
  99. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/data/test_rss.py +0 -0
  100. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/jupyter/test_basic_jupyter.py +0 -0
  101. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/jupyter/test_examples.py +0 -0
  102. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/jupyter/test_interactive.py +0 -0
  103. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/llm/test_chat.py +0 -0
  104. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/llm/test_embedding.py +0 -0
  105. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/operations/test_filtering.py +0 -0
  106. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/operations/test_transforms.py +0 -0
  107. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/pipe/test_core.py +0 -0
  108. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/pipe/test_fork.py +0 -0
  109. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/pipe/test_io.py +0 -0
  110. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/pipe/test_math.py +0 -0
  111. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/util/test_collections.py +0 -0
  112. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/talkpipe/util/test_util.py +0 -0
  113. {talkpipe-0.4.2 → talkpipe-0.5.0}/tests/testutils.py +0 -0
@@ -1,6 +1,33 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.2 (in progress)
3
+ ## in development
4
+ ### New Segments and Sources
5
+ - lambda expression integration
6
+ - **lambda** - lets one write lambda expressions with limited, but common data manipulation segments. This will
7
+ eliminate the need for most small, simple segments, even gt, lt, etc.
8
+ - **lambdaFilter** - filters based on a lambda expression. Uses the same syntax as lambda.
9
+ - **readEmail** - source for reading email from an IMAP server, along with helper functions
10
+ - **sign** and **verify** for signing and verifying data. Also added utility functions for creating keys and associated
11
+ functions.
12
+ - **jsonReceiver** - both a source and an app for opening an endpoint that can receive json and pass it through a
13
+ chatterlang script
14
+
15
+ ### Improvements
16
+ - Broad support for configuration and environment variables. When a parameter to a segment or source starts with a dollar sign (e.g. $myval),
17
+ the system will look first in the current configuration for the key "myval" and then in the OS' environment variables for TALKPIPE_myval.
18
+ - Added a safe lambda expression compiler to data_manipulation. Enables only basic data manipulation functionality
19
+ - Use can specify custom user-agent strings via a configuration file or environment variable for web page
20
+ downloader.
21
+ - Renamed threading.py to thread_ops.py to avoid name collision
22
+ - Renamed scriptendpoint.py to chatterlang_server.py for clarity
23
+ - Documentation improvements
24
+ - Moved code closing mongo connections to the end of the internal loop and out of the del code. It was causing
25
+ error when being disposed as python was exiting.
26
+ - Changed n_iter to max_iter to resolve warning with tsne.
27
+ - Adjust n_neighbors in umap when larger than the number of points to resolve warning
28
+ - Added chatterlang_scipt to the package installer so the user can run talkpipe.app.runscript from the commandline
29
+
30
+ ## 0.4.2
4
31
  ### New Segments
5
32
  - snippet - lets the script writer either load chatterlang code from a file or provide it as a parameter.
6
33
  - Abstracted the comparison filter used for gt and lt
@@ -55,4 +82,4 @@ installed. This works on at least windows and linux
55
82
  - Updated OpenAI source so that it can support guided generation (and thus the llmScore segment)
56
83
 
57
84
  ## 0.2.1 (2025-02-21)
58
- - Catch all ConnectionErrors when downloading the robots.txt file.
85
+ - Catch all ConnectionErrors when downloading the robots.txt file.
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: talkpipe
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Python internal and external DSL for writing generative AI analytics
5
5
  Author-email: Travis Bauer <tlbauer@sandia.gov>
6
6
  Maintainer-email: Travis Bauer <tlbauer@sandia.gov>
7
+ License-Expression: Apache-2.0
7
8
  Project-URL: Homepage, https://github.com/sandialabs/talkpipe
8
9
  Keywords: ai
9
10
  Classifier: Development Status :: 3 - Alpha
10
11
  Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.11
14
14
  Description-Content-Type: text/markdown
@@ -31,6 +31,8 @@ Requires-Dist: ipywidgets
31
31
  Requires-Dist: pymongo
32
32
  Requires-Dist: umap-learn
33
33
  Requires-Dist: scikit-learn
34
+ Requires-Dist: cryptography
35
+ Requires-Dist: uvicorn
34
36
  Provides-Extra: dev
35
37
  Requires-Dist: pytest; extra == "dev"
36
38
  Requires-Dist: pytest-mock; extra == "dev"
@@ -14,10 +14,10 @@ description = "Python internal and external DSL for writing generative AI analyt
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.11"
16
16
  keywords = ["ai"]
17
+ license = "Apache-2.0"
17
18
  classifiers = [
18
19
  "Development Status :: 3 - Alpha",
19
20
  "Programming Language :: Python :: 3",
20
- "License :: OSI Approved :: Apache Software License",
21
21
  "Operating System :: OS Independent",
22
22
  ]
23
23
  dependencies = [
@@ -38,7 +38,9 @@ dependencies = [
38
38
  'ipywidgets',
39
39
  'pymongo',
40
40
  'umap-learn',
41
- 'scikit-learn'
41
+ 'scikit-learn',
42
+ 'cryptography',
43
+ 'uvicorn'
42
44
  ]
43
45
  dynamic = ["version"]
44
46
 
@@ -51,8 +53,11 @@ dev = [
51
53
  ]
52
54
 
53
55
  [project.scripts]
54
- chatterlang_server = "talkpipe.app.scriptendpoint:main"
56
+ chatterlang_server = "talkpipe.app.chatterlang_server:main"
57
+ chatterlang_script = "talkpipe.app.runscript:main"
55
58
  talkpipe_ref = "talkpipe.app.unit_documentation_analyzer:go"
59
+ talkpipe_genkeys = "talkpipe.operations.crypto:generate_keys_cli"
60
+ talkpipe_endpoint = "talkpipe.app.apiendpoint:go"
56
61
 
57
62
  [tool.setuptools.packages.find]
58
63
  where = ["src"]
@@ -66,4 +71,4 @@ where = ["src"]
66
71
  #use defaults
67
72
 
68
73
  [project.urls]
69
- Homepage = "https://github.com/sandialabs/talkpipe"
74
+ Homepage = "https://github.com/sandialabs/talkpipe"
@@ -13,3 +13,5 @@ from talkpipe.app.chatcli import *
13
13
  from talkpipe.operations.filtering import *
14
14
  from talkpipe.operations.transforms import *
15
15
  from talkpipe.operations.matrices import *
16
+ from talkpipe.operations.signatures import *
17
+ from talkpipe.app.apiendpoint import *
File without changes
@@ -0,0 +1,407 @@
1
+ """
2
+ FastAPI JSON Receiver Server
3
+ Receives JSON data via HTTP and processes it with a configurable function
4
+ """
5
+ from typing import Union
6
+ import logging
7
+ import argparse
8
+ from fastapi.responses import FileResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi import FastAPI, HTTPException, Depends, Header
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from fastapi.responses import HTMLResponse
13
+ from pydantic import BaseModel
14
+ from typing import Optional, List, Any, Dict, Callable
15
+ from datetime import datetime
16
+ import uvicorn
17
+ import json
18
+ from pathlib import Path
19
+ import threading
20
+ from queue import Queue
21
+ from talkpipe.pipe.core import AbstractSource
22
+ from talkpipe.chatterlang import register_source
23
+ from talkpipe.chatterlang import compile
24
+ from talkpipe.util.config import get_config
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Models
29
+ class DataResponse(BaseModel):
30
+ status: str
31
+ message: str
32
+ data: Dict[str, Any]
33
+ timestamp: datetime
34
+
35
+ class DataHistory(BaseModel):
36
+ entries: List[dict]
37
+ count: int
38
+
39
+ class JSONReceiver:
40
+ """JSON Data Receiver Server Class - supports multiple instances"""
41
+
42
+ def __init__(
43
+ self,
44
+ host: str = "0.0.0.0",
45
+ port: int = 9999,
46
+ api_key: str = "your-secret-key-here",
47
+ require_auth: bool = False,
48
+ processor_func: Callable[[Dict[str, Any]], Any] = None,
49
+ title: str = "JSON Data Receiver",
50
+ history_length: int = 1000
51
+ ):
52
+ self.host = host
53
+ self.port = port
54
+ self.api_key = api_key
55
+ self.require_auth = require_auth
56
+ self.history = []
57
+ self.title = title
58
+ self.history_length = history_length
59
+
60
+ # Set up processor function
61
+ self.processor_function = processor_func or self._default_print_processor
62
+
63
+ # Create FastAPI app instance
64
+ self.app = FastAPI(title=f"{title} (Port {port})", version="1.0.0")
65
+
66
+ # Mount favicon.ico directly to root
67
+ @self.app.get("/favicon.ico")
68
+ async def favicon():
69
+ favicon_path = Path(__file__).parent / 'static' / 'favicon.ico'
70
+ if favicon_path.exists():
71
+ return FileResponse(favicon_path)
72
+ else:
73
+ raise HTTPException(status_code=404, detail="Favicon not found")
74
+
75
+
76
+ # Configure middleware
77
+ self._setup_middleware()
78
+
79
+ # Configure routes
80
+ self._setup_routes()
81
+
82
+ # Server instance for stopping
83
+ self.server = None
84
+ self.server_thread = None
85
+
86
+ def _setup_middleware(self):
87
+ """Configure CORS middleware"""
88
+ self.app.add_middleware(
89
+ CORSMiddleware,
90
+ allow_origins=["*"],
91
+ allow_credentials=True,
92
+ allow_methods=["*"],
93
+ allow_headers=["*"],
94
+ )
95
+
96
+ def _setup_routes(self):
97
+ """Configure all API routes"""
98
+
99
+ @self.app.get("/", response_class=HTMLResponse)
100
+ async def root():
101
+ return self._get_html_interface()
102
+
103
+ @self.app.post("/process", response_model=DataResponse)
104
+ async def process_json(data: Dict[str, Any], api_key: str = Depends(self._verify_api_key)):
105
+ return await self._process_json(data)
106
+
107
+ @self.app.get("/history", response_model=DataHistory)
108
+ async def get_history(limit: int = 50, api_key: str = Depends(self._verify_api_key)):
109
+ return self._get_history(limit)
110
+
111
+ @self.app.delete("/history")
112
+ async def clear_history(api_key: str = Depends(self._verify_api_key)):
113
+ return self._clear_history()
114
+
115
+ @self.app.get("/health")
116
+ async def health_check():
117
+ return {"status": "healthy", "timestamp": datetime.now(), "port": self.port}
118
+
119
+ async def _verify_api_key(self, x_api_key: Optional[str] = Header(None)):
120
+ """Dependency for API key validation"""
121
+ if self.require_auth and x_api_key != self.api_key:
122
+ raise HTTPException(status_code=403, detail="Invalid API key")
123
+ return x_api_key
124
+
125
+ def _default_print_processor(self, data: Dict[str, Any]) -> str:
126
+ """Default processor: print JSON data to stdout"""
127
+ try:
128
+ print(f"[Port {self.port}] Received JSON: {json.dumps(data, indent=2, default=str)}")
129
+ return "Data printed successfully"
130
+ except Exception as e:
131
+ logger.error(f"Error processing data: {e}")
132
+ raise HTTPException(status_code=500, detail=f"Processing error: {e}")
133
+
134
+ def _save_to_history(self, data: Dict[str, Any]):
135
+ self.history.append(data)
136
+ while len(self.history) > self.history_length:
137
+ self.history.pop(0)
138
+
139
+ async def _process_json(self, data: Dict[str, Any]) -> DataResponse:
140
+ """Process JSON data with the configured processor function"""
141
+
142
+ # Log the request
143
+ logger.info(f"[{datetime.now().strftime('%H:%M:%S')}] Port {self.port}: Processing JSON data")
144
+
145
+ try:
146
+ # Process the data
147
+ result = self.processor_function(data)
148
+
149
+ # Save to history
150
+ self._save_to_history(data)
151
+
152
+ # Determine message based on result
153
+ if isinstance(result, str):
154
+ message = result
155
+ else:
156
+ message = "Data processed successfully"
157
+
158
+ return DataResponse(
159
+ status="success",
160
+ message=message,
161
+ data=data,
162
+ timestamp=datetime.now()
163
+ )
164
+
165
+ except HTTPException:
166
+ # Re-raise HTTP exceptions from the processor
167
+ raise
168
+ except Exception as e:
169
+ logger.error(f"Error processing data: {e}")
170
+ raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}")
171
+
172
+ def _get_history(self, limit: int) -> DataHistory:
173
+ """Get processing history"""
174
+ return DataHistory(entries=self.history, count=len(self.history))
175
+
176
+ def _clear_history(self):
177
+ """Clear processing history"""
178
+ self.history.clear()
179
+ logger.info(f"Port {self.port}: History cleared")
180
+ return {"status": "success", "message": "History cleared", "timestamp": datetime.now()}
181
+
182
+ def _get_html_interface(self) -> str:
183
+ """Get the HTML interface with port-specific URLs"""
184
+ return f"""
185
+ <!DOCTYPE html>
186
+ <html>
187
+ <head>
188
+ <title>{self.title} - Port {self.port}</title>
189
+ <link rel="icon" type="image/ico" href="/favicon.ico">
190
+ <style>
191
+ body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }}
192
+ input, button, textarea {{ font-size: 16px; padding: 10px; margin: 5px; }}
193
+ textarea {{ width: 500px; height: 150px; font-family: monospace; }}
194
+ .status {{ margin-top: 20px; padding: 10px; border-radius: 5px; }}
195
+ .success {{ background-color: #d4edda; color: #155724; }}
196
+ .error {{ background-color: #f8d7da; color: #721c24; }}
197
+ .history {{ margin-top: 30px; }}
198
+ .history-item {{ padding: 5px; border-bottom: 1px solid #ddd; }}
199
+ code {{ background-color: #f4f4f4; padding: 2px 5px; border-radius: 3px; }}
200
+ .port-info {{ background-color: #e3f2fd; padding: 10px; border-radius: 5px; margin-bottom: 20px; }}
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <div class="port-info">
205
+ <h1>{self.title}</h1>
206
+ <p><strong>Server Port:</strong> {self.port}</p>
207
+ <p><strong>History Length:</strong> {len(self.history)}, max length={self.history_length}</p>
208
+ </div>
209
+
210
+ <p>Send JSON data to process it on this machine:</p>
211
+
212
+ <div>
213
+ <textarea id="jsonData" placeholder='{{"example": "data", "number": 42}}'></textarea><br>
214
+ <button onclick="sendData()">Send JSON Data</button>
215
+ </div>
216
+
217
+ <div id="status"></div>
218
+
219
+ <div class="history">
220
+ <h3>API Endpoints:</h3>
221
+ <ul>
222
+ <li><code>POST /process</code> - Send JSON data for processing</li>
223
+ <li><code>GET /history</code> - Get processing history</li>
224
+ <li><code>DELETE /history</code> - Clear processing history</li>
225
+ <li><code>GET /health</code> - Health check</li>
226
+ <li><code>GET /docs</code> - API documentation</li>
227
+ </ul>
228
+
229
+ <h3>Usage Examples:</h3>
230
+ <pre>
231
+ # Using curl:
232
+ curl -X POST http://localhost:{self.port}/process \\
233
+ -H "Content-Type: application/json" \\
234
+ -d '{{"message": "hello", "value": 123}}'
235
+
236
+ # Using PowerShell:
237
+ Invoke-RestMethod -Uri http://localhost:{self.port}/process \\
238
+ -Method POST -ContentType "application/json" \\
239
+ -Body '{{"key": "value", "number": 42}}'
240
+
241
+ # Using Python:
242
+ import requests
243
+ response = requests.post('http://localhost:{self.port}/process',
244
+ json={{'key': 'value', 'number': 42}})
245
+ print(response.json())</pre>
246
+ </div>
247
+
248
+ <script>
249
+ async function sendData() {{
250
+ const jsonText = document.getElementById('jsonData').value;
251
+ const status = document.getElementById('status');
252
+
253
+ try {{
254
+ const data = JSON.parse(jsonText);
255
+
256
+ const response = await fetch('/process', {{
257
+ method: 'POST',
258
+ headers: {{'Content-Type': 'application/json'}},
259
+ body: JSON.stringify(data)
260
+ }});
261
+
262
+ const result = await response.json();
263
+
264
+ if (response.ok) {{
265
+ status.className = 'status success';
266
+ status.textContent = 'JSON data processed successfully on port {self.port}!';
267
+ }} else {{
268
+ status.className = 'status error';
269
+ status.textContent = 'Error: ' + result.detail;
270
+ }}
271
+ }} catch (error) {{
272
+ status.className = 'status error';
273
+ status.textContent = 'Error: ' + error.message;
274
+ }}
275
+
276
+ setTimeout(() => status.textContent = '', 3000);
277
+ }}
278
+ </script>
279
+ </body>
280
+ </html>
281
+ """
282
+
283
+ def set_processor_function(self, func: Callable[[Dict[str, Any]], Any]):
284
+ """Set the function used to process incoming JSON data"""
285
+ self.processor_function = func
286
+ logger.info(f"Port {self.port}: Processor function set to: {func.__name__}")
287
+
288
+ def start(self, background: bool = False):
289
+ """Start the FastAPI server"""
290
+ logger.info(f"Starting JSON Data Receiver on http://{self.host}:{self.port}")
291
+ logger.info(f"API documentation: http://{self.host}:{self.port}/docs")
292
+ if self.require_auth:
293
+ logger.info(f"Port {self.port}: API key authentication: ENABLED")
294
+
295
+ processor_name = self.processor_function.__name__
296
+ logger.info(f"Port {self.port}: Using processor function: {processor_name}")
297
+
298
+ if background:
299
+ self.server_thread = threading.Thread(
300
+ target=lambda: uvicorn.run(self.app, host=self.host, port=self.port),
301
+ daemon=True
302
+ )
303
+ self.server_thread.start()
304
+ logger.info(f"Port {self.port}: Server started in background thread")
305
+ else:
306
+ uvicorn.run(self.app, host=self.host, port=self.port)
307
+
308
+ def stop(self):
309
+ """Stop the server (only works if started in background)"""
310
+ if self.server_thread and self.server_thread.is_alive():
311
+ logger.info(f"Port {self.port}: Stopping server...")
312
+ # Note: uvicorn doesn't provide a clean stop method when run this way
313
+ # For production use, consider using uvicorn.Server with proper lifecycle management
314
+
315
+ @register_source("jsonReceiver")
316
+ class JSONReceiverSegment(AbstractSource):
317
+ """Segment for receiving JSON data via FastAPI"""
318
+
319
+ def __init__(self, port: Union[int,str] = 9999, host: str = "0.0.0.0",
320
+ api_key: str = None, require_auth: bool = False):
321
+ super().__init__()
322
+ self.port = int(port)
323
+ self.host = host
324
+ self.api_key = api_key
325
+ self.require_auth = require_auth
326
+ self.queue = Queue(maxsize=1000)
327
+
328
+ self.receiver = JSONReceiver(
329
+ host=host,
330
+ port=self.port,
331
+ api_key=api_key,
332
+ require_auth=require_auth,
333
+ title=f"JSON Receiver Segment (Port {port})",
334
+ processor_func=self.process_data
335
+ )
336
+ self.receiver.start(background=True)
337
+ logger.info(f"Finished initializing JSONReceiverSegment on port {port}")
338
+
339
+ def process_data(self, data: Dict[str, Any]) -> str:
340
+ """Process incoming JSON data and add it to the queue"""
341
+ try:
342
+ self.queue.put(data, block=True, timeout=60)
343
+ return f"Data received and queued: {data}"
344
+ except Exception as e:
345
+ logger.error(f"Error processing data: {e}")
346
+ return f"Error processing data: {str(e)}"
347
+
348
+ def generate(self):
349
+ print("Starting JSONReceiverSegment generator...")
350
+ while True:
351
+ # Wait for data to be available in the queue
352
+ print("Waiting for data...")
353
+ # This will block until data is available
354
+ yield self.queue.get(block=True, timeout=None)
355
+
356
+ def go():
357
+
358
+ parser = argparse.ArgumentParser(description='FastAPI JSON Data Receiver')
359
+ parser.add_argument('-p', '--port', type=int, default=9999,
360
+ help='Port to listen on (default: 9999)')
361
+ parser.add_argument('-o', '--host', default='0.0.0.0',
362
+ help='Host to bind to (default: 0.0.0.0)')
363
+ parser.add_argument('--api-key', help='Set API key for authentication')
364
+ parser.add_argument('--require-auth', action='store_true',
365
+ help='Require API key authentication')
366
+ parser.add_argument('--title', default='JSON Data Receiver',
367
+ help='Title for the FastAPI application')
368
+ parser.add_argument('--script-var', default=None,
369
+ help='Configuration key containing script to run')
370
+ parser.add_argument('--script', default=None,
371
+ help='Chatterlang script to run on received data')
372
+
373
+ args = parser.parse_args()
374
+
375
+
376
+ script = None
377
+ if args.script_var and args.script:
378
+ raise ValueError("Cannot specify both --script-var and --script. Use one or the other.")
379
+ if args.script_var:
380
+ script = get_config(args.script_var)
381
+ if not script:
382
+ raise ValueError(f"No script found for variable '{args.script_var}' in configuration.")
383
+ else:
384
+ script = args.script
385
+
386
+ if script:
387
+ # Compile the Chatterlang script
388
+ compiled_script = compile(script)
389
+ compiled_script = compiled_script.asFunction(single_in=True, single_out=False)
390
+ else:
391
+ # Default processor function if no script is provided
392
+ compiled_script = None
393
+
394
+ receiver = JSONReceiver(
395
+ host=args.host,
396
+ port=args.port,
397
+ api_key=args.api_key,
398
+ require_auth=args.require_auth,
399
+ title=args.title,
400
+ processor_func=compiled_script,
401
+ )
402
+
403
+ # Start the server
404
+ receiver.start(background=False)
405
+
406
+ if __name__ == "__main__":
407
+ go()
@@ -1011,7 +1011,7 @@ def main():
1011
1011
 
1012
1012
  print(f"Starting TalkPipe server at http://{args.host}:{args.port}")
1013
1013
  uvicorn.run(
1014
- "talkpipe.app.scriptendpoint:app",
1014
+ "talkpipe.app.chatterlang_server:app",
1015
1015
  host=args.host,
1016
1016
  port=args.port,
1017
1017
  reload=args.reload