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