lam-cli 0.1.2__tar.gz → 0.1.4__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.
- lam_cli-0.1.4/PKG-INFO +53 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam/lam.py +136 -95
- lam_cli-0.1.4/lam_cli.egg-info/PKG-INFO +53 -0
- lam_cli-0.1.4/setup.py +62 -0
- lam_cli-0.1.2/PKG-INFO +0 -240
- lam_cli-0.1.2/lam_cli.egg-info/PKG-INFO +0 -240
- lam_cli-0.1.2/setup.py +0 -62
- {lam_cli-0.1.2 → lam_cli-0.1.4}/LICENSE +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/README.md +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam/__init__.py +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam_cli.egg-info/SOURCES.txt +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam_cli.egg-info/dependency_links.txt +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam_cli.egg-info/entry_points.txt +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam_cli.egg-info/requires.txt +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/lam_cli.egg-info/top_level.txt +0 -0
- {lam_cli-0.1.2 → lam_cli-0.1.4}/setup.cfg +0 -0
lam_cli-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lam-cli
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Secure data transformation tool supporting JQ and JavaScript (Bun)
|
|
5
|
+
Home-page: https://github.com/laminar-run/lam
|
|
6
|
+
Author: Laminar Run, Inc.
|
|
7
|
+
Author-email: connect@laminar.run
|
|
8
|
+
License: GPLv3
|
|
9
|
+
Project-URL: Documentation, https://docs.laminar.run
|
|
10
|
+
Project-URL: Source, https://github.com/laminar-run/lam
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/laminar-run/lam/issues
|
|
12
|
+
Keywords: laminar,api,integration,transformation,json,jq,javascript,bun
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: backoff>=2.2.1
|
|
29
|
+
Requires-Dist: certifi>=2024.12.14
|
|
30
|
+
Requires-Dist: charset-normalizer>=3.3.2
|
|
31
|
+
Requires-Dist: click>=8.1.7
|
|
32
|
+
Requires-Dist: idna>=3.7
|
|
33
|
+
Requires-Dist: logtail-python>=0.2.2
|
|
34
|
+
Requires-Dist: monotonic>=1.6
|
|
35
|
+
Requires-Dist: msgpack>=1.0.8
|
|
36
|
+
Requires-Dist: posthog>=3.4.0
|
|
37
|
+
Requires-Dist: psutil>=5.9.0
|
|
38
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
39
|
+
Requires-Dist: requests>=2.32.3
|
|
40
|
+
Requires-Dist: six>=1.16.0
|
|
41
|
+
Requires-Dist: urllib3>=2.2.2
|
|
42
|
+
Dynamic: author
|
|
43
|
+
Dynamic: author-email
|
|
44
|
+
Dynamic: classifier
|
|
45
|
+
Dynamic: description-content-type
|
|
46
|
+
Dynamic: home-page
|
|
47
|
+
Dynamic: keywords
|
|
48
|
+
Dynamic: license
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
Dynamic: project-url
|
|
51
|
+
Dynamic: requires-dist
|
|
52
|
+
Dynamic: requires-python
|
|
53
|
+
Dynamic: summary
|
|
@@ -8,7 +8,7 @@ import socket
|
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
10
|
import tempfile
|
|
11
|
-
from datetime import datetime
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
12
|
from enum import Enum
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Any, Dict, Optional, Tuple, Union
|
|
@@ -22,9 +22,12 @@ from posthog import Posthog
|
|
|
22
22
|
posthog = Posthog(project_api_key='phc_wfeHFG0p5yZIdBpjVYy00o5x1HbEpggdMzIuFYgNPSK',
|
|
23
23
|
host='https://app.posthog.com')
|
|
24
24
|
|
|
25
|
-
logging
|
|
25
|
+
# Configure logging with UTC timezone
|
|
26
|
+
logging.Formatter.converter = lambda *args: datetime.now(timezone.utc).timetuple()
|
|
27
|
+
logging.basicConfig(level=logging.DEBUG,
|
|
26
28
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
30
|
+
logging.getLogger('urllib3').setLevel(logging.WARNING) # Suppress urllib3 logs
|
|
28
31
|
|
|
29
32
|
handler = LogtailHandler(source_token="TYz3WrrvC8ehYjXdAEGGyiDp")
|
|
30
33
|
logger.addHandler(handler)
|
|
@@ -47,20 +50,20 @@ class ResourceLimitError(LAMError):
|
|
|
47
50
|
|
|
48
51
|
def check_resource_limits(modules_dir: Optional[Path] = None) -> None:
|
|
49
52
|
"""Check system resource availability"""
|
|
50
|
-
|
|
53
|
+
logger.debug("Checking system resource limits")
|
|
51
54
|
disk = shutil.disk_usage(tempfile.gettempdir())
|
|
52
55
|
if disk.free < 100 * 1024 * 1024: # 100MB minimum
|
|
56
|
+
logger.critical("Insufficient disk space: %dMB free", disk.free // (1024*1024))
|
|
53
57
|
raise ResourceLimitError("Insufficient disk space")
|
|
54
58
|
|
|
55
|
-
# Check shared modules size if provided
|
|
56
59
|
if modules_dir and modules_dir.exists():
|
|
57
60
|
modules_size = sum(
|
|
58
61
|
os.path.getsize(os.path.join(dirpath, filename))
|
|
59
62
|
for dirpath, _, filenames in os.walk(modules_dir)
|
|
60
63
|
for filename in filenames
|
|
61
64
|
)
|
|
62
|
-
if modules_size > 500 * 1024 * 1024:
|
|
63
|
-
logger.warning("
|
|
65
|
+
if modules_size > 500 * 1024 * 1024:
|
|
66
|
+
logger.warning("Cleaning oversized modules directory (%dMB)", modules_size//(1024*1024))
|
|
64
67
|
shutil.rmtree(modules_dir)
|
|
65
68
|
modules_dir.mkdir(exist_ok=True)
|
|
66
69
|
|
|
@@ -71,7 +74,6 @@ class Stats:
|
|
|
71
74
|
self.memory_start = self.get_memory_usage()
|
|
72
75
|
|
|
73
76
|
def get_memory_usage(self):
|
|
74
|
-
import psutil
|
|
75
77
|
process = psutil.Process()
|
|
76
78
|
return process.memory_info().rss
|
|
77
79
|
|
|
@@ -120,17 +122,17 @@ class Engine:
|
|
|
120
122
|
class JQEngine(Engine):
|
|
121
123
|
"""JQ execution engine"""
|
|
122
124
|
def validate_environment(self) -> bool:
|
|
125
|
+
logger.debug("Validating JQ environment")
|
|
123
126
|
return shutil.which("jq") is not None
|
|
124
127
|
|
|
125
128
|
def execute(self, program_file: str, input_data: str) -> Tuple[Union[Dict, str], Optional[str]]:
|
|
126
129
|
logger.info(f"Executing JQ script: {program_file}")
|
|
127
130
|
|
|
128
131
|
try:
|
|
129
|
-
# Parse JQ program
|
|
130
132
|
with open(program_file, 'r') as file:
|
|
131
133
|
jq_script = ''.join(line for line in file if not line.strip().startswith('#'))
|
|
134
|
+
logger.debug("Loaded JQ script: %d characters", len(jq_script))
|
|
132
135
|
|
|
133
|
-
# Run JQ
|
|
134
136
|
process = subprocess.Popen(
|
|
135
137
|
["jq", "-c", jq_script],
|
|
136
138
|
stdin=subprocess.PIPE,
|
|
@@ -139,59 +141,82 @@ class JQEngine(Engine):
|
|
|
139
141
|
text=True
|
|
140
142
|
)
|
|
141
143
|
|
|
144
|
+
logger.debug("Starting JQ process PID %d", process.pid)
|
|
142
145
|
output, error = process.communicate(input=input_data)
|
|
143
146
|
|
|
144
147
|
if error:
|
|
148
|
+
logger.error("JQ error output: %s", error.strip())
|
|
145
149
|
raise ProcessingError(error)
|
|
146
150
|
|
|
147
151
|
# Handle output
|
|
148
152
|
try:
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
output_lines = [line.strip() for line in output.splitlines() if line.strip()]
|
|
154
|
+
logger.debug(f"Found {len(output_lines)} JSON objects in output")
|
|
155
|
+
|
|
156
|
+
if len(output_lines) > 1:
|
|
157
|
+
parsed = [json.loads(line) for line in output_lines]
|
|
158
|
+
logger.info(f"Processed {len(parsed)} JSON objects")
|
|
159
|
+
return {"lam.result": parsed}, None
|
|
160
|
+
elif len(output_lines) == 1:
|
|
161
|
+
result = json.loads(output_lines[0])
|
|
162
|
+
logger.info("Processed single JSON object")
|
|
163
|
+
return result, None
|
|
164
|
+
else:
|
|
165
|
+
logger.info("No JSON objects in output")
|
|
166
|
+
return {"lam.error": "No JSON objects in output"}, "No JSON objects in output"
|
|
167
|
+
|
|
168
|
+
except json.JSONDecodeError as e:
|
|
151
169
|
return {"lam.result": output}, None
|
|
152
170
|
|
|
153
171
|
except Exception as e:
|
|
172
|
+
logger.exception("JQ execution failed")
|
|
154
173
|
self.track_event('lam.jq.error', {'error': str(e)})
|
|
155
174
|
return {"lam.error": str(e)}, str(e)
|
|
156
175
|
|
|
157
176
|
class BunEngine(Engine):
|
|
158
|
-
"""Bun JavaScript execution engine"""
|
|
177
|
+
"""Bun JavaScript execution engine with enhanced logging"""
|
|
159
178
|
def __init__(self, *args, **kwargs):
|
|
160
179
|
super().__init__(*args, **kwargs)
|
|
161
|
-
# Create a persistent temp directory for node_modules
|
|
162
180
|
self.modules_dir = Path(tempfile.gettempdir()) / "lam_modules"
|
|
163
181
|
self.modules_dir.mkdir(exist_ok=True)
|
|
164
182
|
self._setup_shared_modules()
|
|
165
183
|
|
|
166
184
|
self.runtime_template = '''
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
const logs = [];
|
|
186
|
+
const originalLog = console.log;
|
|
187
|
+
const originalError = console.error;
|
|
188
|
+
const originalWarn = console.warn;
|
|
171
189
|
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
console.log = (...args) => logs.push({ type: 'log', message: args.map(String).join(' ') });
|
|
191
|
+
console.error = (...args) => {
|
|
192
|
+
originalError(...args); // Keep error output for debugging
|
|
193
|
+
logs.push({ type: 'error', message: args.map(String).join(' ') });
|
|
194
|
+
};
|
|
195
|
+
console.warn = (...args) => logs.push({ type: 'warn', message: args.map(String).join(' ') });
|
|
174
196
|
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
warn: console.warn
|
|
197
|
+
// Keep original stdout for result output
|
|
198
|
+
const writeResult = (obj) => {
|
|
199
|
+
console.error("Writing result:", JSON.stringify(obj, null, 2));
|
|
200
|
+
originalLog(JSON.stringify(obj));
|
|
180
201
|
};
|
|
181
|
-
globalThis.console = secureConsole;
|
|
182
202
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
const _ = require('lodash');
|
|
204
|
+
const { format, parseISO } = require('date-fns');
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
_,
|
|
208
|
+
format,
|
|
209
|
+
parseISO,
|
|
210
|
+
logs,
|
|
211
|
+
writeResult
|
|
212
|
+
};
|
|
187
213
|
'''
|
|
188
|
-
|
|
214
|
+
|
|
189
215
|
def _setup_shared_modules(self):
|
|
190
216
|
"""Setup shared node_modules once"""
|
|
191
217
|
if not (self.modules_dir / "node_modules").exists():
|
|
192
|
-
|
|
218
|
+
logger.info("Initializing shared modules directory")
|
|
193
219
|
package_json = {
|
|
194
|
-
"type": "module",
|
|
195
220
|
"dependencies": {
|
|
196
221
|
"lodash": "^4.17.21",
|
|
197
222
|
"date-fns": "^2.30.0"
|
|
@@ -200,26 +225,25 @@ class BunEngine(Engine):
|
|
|
200
225
|
with open(self.modules_dir / "package.json", "w") as f:
|
|
201
226
|
json.dump(package_json, f, indent=2)
|
|
202
227
|
|
|
203
|
-
# Install dependencies once
|
|
204
228
|
try:
|
|
205
|
-
|
|
229
|
+
logger.debug("Installing shared dependencies")
|
|
230
|
+
result = subprocess.run(
|
|
206
231
|
[self.get_bun_path(), "install"],
|
|
207
232
|
cwd=self.modules_dir,
|
|
208
233
|
check=True,
|
|
209
234
|
capture_output=True,
|
|
210
235
|
text=True,
|
|
211
|
-
timeout=30
|
|
236
|
+
timeout=30
|
|
212
237
|
)
|
|
238
|
+
logger.debug("Dependency install output: %s", result.stdout)
|
|
213
239
|
except subprocess.CalledProcessError as e:
|
|
214
|
-
logger.error(
|
|
215
|
-
raise ProcessingError(
|
|
216
|
-
f"Failed to set up JavaScript environment: {e.stderr}"
|
|
217
|
-
) from e
|
|
240
|
+
logger.error("Dependency install failed: %s", e.stderr)
|
|
241
|
+
raise ProcessingError(f"Environment setup failed: {e.stderr}") from e
|
|
218
242
|
|
|
219
243
|
def create_wrapper(self, input_data: str, user_script: str) -> str:
|
|
220
244
|
"""Create the wrapper script with proper escaping"""
|
|
221
245
|
return f'''
|
|
222
|
-
|
|
246
|
+
const {{ _, format, parseISO, logs, writeResult }} = require('./runtime.js');
|
|
223
247
|
|
|
224
248
|
// Utility function to handle circular references in JSON.stringify
|
|
225
249
|
function safeStringify(obj) {{
|
|
@@ -250,7 +274,7 @@ class BunEngine(Engine):
|
|
|
250
274
|
// Parse input safely
|
|
251
275
|
let input;
|
|
252
276
|
try {{
|
|
253
|
-
input = {input_data};
|
|
277
|
+
input = JSON.parse({json.dumps(input_data)});
|
|
254
278
|
}} catch (e) {{
|
|
255
279
|
throw new Error(`Failed to parse input data: ${{e.message}}`);
|
|
256
280
|
}}
|
|
@@ -269,17 +293,15 @@ class BunEngine(Engine):
|
|
|
269
293
|
// Execute transform
|
|
270
294
|
const result = transform(input);
|
|
271
295
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}}
|
|
277
|
-
throw new Error(`Result is not JSON serializable: ${{e.message}}`);
|
|
278
|
-
}}
|
|
296
|
+
// Output result after transform
|
|
297
|
+
writeResult({{
|
|
298
|
+
result,
|
|
299
|
+
logs
|
|
300
|
+
}});
|
|
279
301
|
}} catch (error) {{
|
|
280
302
|
console.error(JSON.stringify({{
|
|
281
303
|
error: error.message,
|
|
282
|
-
stack: error.stack,
|
|
304
|
+
stack: error.stack?.split('\\n') || [],
|
|
283
305
|
type: error.constructor.name
|
|
284
306
|
}}));
|
|
285
307
|
process.exit(1);
|
|
@@ -292,10 +314,11 @@ class BunEngine(Engine):
|
|
|
292
314
|
runtime_path = temp_dir / "runtime.js"
|
|
293
315
|
with open(runtime_path, "w") as f:
|
|
294
316
|
f.write(self.runtime_template)
|
|
317
|
+
logger.debug("Runtime file written to: %s", runtime_path)
|
|
295
318
|
|
|
296
319
|
# Symlink node_modules from shared directory
|
|
297
320
|
os.symlink(self.modules_dir / "node_modules", temp_dir / "node_modules")
|
|
298
|
-
|
|
321
|
+
logger.debug("node_modules symlinked from: %s", self.modules_dir / "node_modules")
|
|
299
322
|
|
|
300
323
|
def validate_environment(self) -> bool:
|
|
301
324
|
# Check multiple locations for bun
|
|
@@ -335,22 +358,23 @@ class BunEngine(Engine):
|
|
|
335
358
|
# Read user script
|
|
336
359
|
with open(program_file, 'r') as f:
|
|
337
360
|
user_script = f.read()
|
|
361
|
+
logger.debug("Loaded user script: %d characters", len(user_script))
|
|
338
362
|
|
|
339
363
|
# Create wrapper script
|
|
340
364
|
wrapper = self.create_wrapper(input_data, user_script)
|
|
341
|
-
|
|
342
365
|
script_path = temp_dir / "script.js"
|
|
343
|
-
with open(script_path,
|
|
366
|
+
with open(script_path, 'w') as f:
|
|
344
367
|
f.write(wrapper)
|
|
368
|
+
logger.debug("Generated wrapper script: %s", script_path)
|
|
345
369
|
|
|
346
370
|
# Execute with Bun
|
|
347
371
|
process = subprocess.Popen(
|
|
348
372
|
[
|
|
349
373
|
self.get_bun_path(),
|
|
350
374
|
"run",
|
|
351
|
-
"--no-fetch",
|
|
352
|
-
"--smol",
|
|
353
|
-
"--silent",
|
|
375
|
+
"--no-fetch",
|
|
376
|
+
"--smol",
|
|
377
|
+
"--silent",
|
|
354
378
|
str(script_path)
|
|
355
379
|
],
|
|
356
380
|
stdout=subprocess.PIPE,
|
|
@@ -358,36 +382,64 @@ class BunEngine(Engine):
|
|
|
358
382
|
text=True,
|
|
359
383
|
cwd=temp_dir
|
|
360
384
|
)
|
|
385
|
+
logger.info("Started Bun process PID %d", process.pid)
|
|
361
386
|
|
|
362
387
|
try:
|
|
363
|
-
output, error = process.communicate(timeout=5)
|
|
388
|
+
output, error = process.communicate(timeout=5)
|
|
389
|
+
logger.debug("Process completed with code %d", process.returncode)
|
|
364
390
|
except subprocess.TimeoutExpired as e:
|
|
391
|
+
logger.warning("Process timeout after 5 seconds")
|
|
365
392
|
process.kill()
|
|
366
|
-
|
|
393
|
+
return {"lam.error": "Script execution timed out"}, "Execution timed out after 5 seconds"
|
|
367
394
|
|
|
368
|
-
|
|
395
|
+
# Handle process errors
|
|
396
|
+
if process.returncode != 0:
|
|
369
397
|
try:
|
|
370
|
-
|
|
398
|
+
# Try to parse structured error from stderr
|
|
399
|
+
error_data = json.loads(error.strip())
|
|
371
400
|
error_msg = error_data.get('error', 'Unknown error')
|
|
372
|
-
|
|
373
|
-
|
|
401
|
+
stack = error_data.get('stack', [])
|
|
402
|
+
|
|
403
|
+
# Format error message
|
|
404
|
+
error_details = {
|
|
405
|
+
"lam.error": error_msg,
|
|
406
|
+
"stack_trace": stack
|
|
407
|
+
}
|
|
408
|
+
return error_details, error_msg
|
|
409
|
+
|
|
374
410
|
except json.JSONDecodeError:
|
|
375
|
-
|
|
376
|
-
|
|
411
|
+
# Fallback to raw error output
|
|
412
|
+
error_msg = error.strip() or "Unknown error"
|
|
413
|
+
return {"lam.error": error_msg}, error_msg
|
|
377
414
|
|
|
415
|
+
# Handle successful output
|
|
378
416
|
try:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
417
|
+
output_data = json.loads(output)
|
|
418
|
+
|
|
419
|
+
# Process JavaScript logs (if any)
|
|
420
|
+
if 'logs' in output_data:
|
|
421
|
+
for log_entry in output_data.get('logs', []):
|
|
422
|
+
if log_entry['type'] == 'error':
|
|
423
|
+
logger.error("[JS] %s", log_entry['message'])
|
|
424
|
+
else:
|
|
425
|
+
logger.debug("[JS] %s", log_entry['message'])
|
|
426
|
+
|
|
427
|
+
result = output_data.get('result', {})
|
|
428
|
+
return result, None
|
|
429
|
+
|
|
430
|
+
except json.JSONDecodeError as e:
|
|
431
|
+
logger.error("Failed to parse output: %s", str(e))
|
|
432
|
+
return {
|
|
433
|
+
"lam.error": "Invalid JSON output",
|
|
434
|
+
"raw_output": output.strip()
|
|
435
|
+
}, "Output format error"
|
|
382
436
|
|
|
383
437
|
except Exception as e:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
})
|
|
390
|
-
return {"lam.error": str(e)}, str(e)
|
|
438
|
+
logger.exception("Execution failed")
|
|
439
|
+
return {
|
|
440
|
+
"lam.error": str(e),
|
|
441
|
+
"type": e.__class__.__name__
|
|
442
|
+
}, str(e)
|
|
391
443
|
|
|
392
444
|
def get_engine(engine_type: str, workspace_id: str, flow_id: str, execution_id: str) -> Engine:
|
|
393
445
|
"""Factory function to get the appropriate execution engine"""
|
|
@@ -409,13 +461,16 @@ def get_engine(engine_type: str, workspace_id: str, flow_id: str, execution_id:
|
|
|
409
461
|
def process_input(input: str) -> Tuple[str, Optional[str]]:
|
|
410
462
|
"""Process and validate input data"""
|
|
411
463
|
if os.path.isfile(input):
|
|
464
|
+
logger.debug("Loading input from file: %s", input)
|
|
412
465
|
with open(input, 'r') as file:
|
|
413
466
|
return file.read(), None
|
|
414
467
|
|
|
415
468
|
try:
|
|
416
469
|
json.loads(input)
|
|
470
|
+
logger.debug("Validated inline JSON input")
|
|
417
471
|
return input, None
|
|
418
472
|
except json.JSONDecodeError as e:
|
|
473
|
+
logger.error("Invalid JSON input: %s", str(e))
|
|
419
474
|
return None, str(e)
|
|
420
475
|
|
|
421
476
|
@click.group()
|
|
@@ -435,16 +490,14 @@ def lam():
|
|
|
435
490
|
def run(program_file: str, input: str, language: str, workspace_id: str,
|
|
436
491
|
flow_id: str, execution_id: str, as_json: bool):
|
|
437
492
|
"""Execute a LAM transformation script"""
|
|
438
|
-
stats = Stats()
|
|
493
|
+
stats = Stats()
|
|
439
494
|
|
|
440
|
-
# Initialize engine
|
|
441
495
|
try:
|
|
442
496
|
engine = get_engine(language, workspace_id, flow_id, execution_id)
|
|
443
497
|
except (ValueError, EnvironmentError) as e:
|
|
444
498
|
click.echo({"lam.error": str(e)}, err=True)
|
|
445
499
|
return
|
|
446
500
|
|
|
447
|
-
# Setup logging
|
|
448
501
|
log_file = engine.get_log_file()
|
|
449
502
|
result_file = engine.get_result_file()
|
|
450
503
|
|
|
@@ -452,25 +505,22 @@ def run(program_file: str, input: str, language: str, workspace_id: str,
|
|
|
452
505
|
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
|
453
506
|
logger.addHandler(file_handler)
|
|
454
507
|
|
|
455
|
-
logger.info(
|
|
508
|
+
logger.info("Starting LAM execution with %s engine", language)
|
|
456
509
|
engine.track_event('lam.run.start', {
|
|
457
510
|
'language': language,
|
|
458
511
|
'program_file': program_file
|
|
459
512
|
})
|
|
460
513
|
|
|
461
514
|
try:
|
|
462
|
-
# Process input
|
|
463
515
|
input_data, error = process_input(input)
|
|
464
516
|
if error:
|
|
465
517
|
raise ProcessingError(f"Invalid input: {error}")
|
|
466
518
|
|
|
467
|
-
# Execute transformation
|
|
468
519
|
result, error = engine.execute(program_file, input_data)
|
|
469
520
|
|
|
470
|
-
# Get final stats
|
|
471
521
|
stats_data = stats.finalize()
|
|
472
|
-
logger.info(
|
|
473
|
-
|
|
522
|
+
logger.info("Execution stats: duration=%.2fms, memory=%.2fMB",
|
|
523
|
+
stats_data['duration_ms'], stats_data['memory_used_mb'])
|
|
474
524
|
|
|
475
525
|
if error:
|
|
476
526
|
click.echo({"lam.error": error}, err=True)
|
|
@@ -480,15 +530,6 @@ def run(program_file: str, input: str, language: str, workspace_id: str,
|
|
|
480
530
|
click.echo(output)
|
|
481
531
|
engine.track_event('lam.run.success', stats_data)
|
|
482
532
|
|
|
483
|
-
# Save result with stats
|
|
484
|
-
result_with_stats = {
|
|
485
|
-
'result': result,
|
|
486
|
-
'stats': stats_data,
|
|
487
|
-
'error': error or None
|
|
488
|
-
}
|
|
489
|
-
logger.info(f"Final stats: {stats_data}")
|
|
490
|
-
|
|
491
|
-
# If result is an array, wrap it in "lam.result"
|
|
492
533
|
if isinstance(result, list):
|
|
493
534
|
result = {"lam.result": result}
|
|
494
535
|
|
|
@@ -497,9 +538,9 @@ def run(program_file: str, input: str, language: str, workspace_id: str,
|
|
|
497
538
|
|
|
498
539
|
except Exception as e:
|
|
499
540
|
stats_data = stats.finalize()
|
|
500
|
-
logger.error(
|
|
501
|
-
logger.error(
|
|
502
|
-
|
|
541
|
+
logger.error("Execution failed: %s", str(e))
|
|
542
|
+
logger.error("Final stats: duration=%.2fms, memory=%.2fMB",
|
|
543
|
+
stats_data['duration_ms'], stats_data['memory_used_mb'])
|
|
503
544
|
click.echo({"lam.error": str(e)}, err=True)
|
|
504
545
|
engine.track_event('lam.run.error', {'error': str(e), **stats_data})
|
|
505
546
|
|
|
@@ -508,4 +549,4 @@ def run(program_file: str, input: str, language: str, workspace_id: str,
|
|
|
508
549
|
logger.removeHandler(file_handler)
|
|
509
550
|
|
|
510
551
|
if __name__ == '__main__':
|
|
511
|
-
lam()
|
|
552
|
+
lam()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lam-cli
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Secure data transformation tool supporting JQ and JavaScript (Bun)
|
|
5
|
+
Home-page: https://github.com/laminar-run/lam
|
|
6
|
+
Author: Laminar Run, Inc.
|
|
7
|
+
Author-email: connect@laminar.run
|
|
8
|
+
License: GPLv3
|
|
9
|
+
Project-URL: Documentation, https://docs.laminar.run
|
|
10
|
+
Project-URL: Source, https://github.com/laminar-run/lam
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/laminar-run/lam/issues
|
|
12
|
+
Keywords: laminar,api,integration,transformation,json,jq,javascript,bun
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: backoff>=2.2.1
|
|
29
|
+
Requires-Dist: certifi>=2024.12.14
|
|
30
|
+
Requires-Dist: charset-normalizer>=3.3.2
|
|
31
|
+
Requires-Dist: click>=8.1.7
|
|
32
|
+
Requires-Dist: idna>=3.7
|
|
33
|
+
Requires-Dist: logtail-python>=0.2.2
|
|
34
|
+
Requires-Dist: monotonic>=1.6
|
|
35
|
+
Requires-Dist: msgpack>=1.0.8
|
|
36
|
+
Requires-Dist: posthog>=3.4.0
|
|
37
|
+
Requires-Dist: psutil>=5.9.0
|
|
38
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
39
|
+
Requires-Dist: requests>=2.32.3
|
|
40
|
+
Requires-Dist: six>=1.16.0
|
|
41
|
+
Requires-Dist: urllib3>=2.2.2
|
|
42
|
+
Dynamic: author
|
|
43
|
+
Dynamic: author-email
|
|
44
|
+
Dynamic: classifier
|
|
45
|
+
Dynamic: description-content-type
|
|
46
|
+
Dynamic: home-page
|
|
47
|
+
Dynamic: keywords
|
|
48
|
+
Dynamic: license
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
Dynamic: project-url
|
|
51
|
+
Dynamic: requires-dist
|
|
52
|
+
Dynamic: requires-python
|
|
53
|
+
Dynamic: summary
|
lam_cli-0.1.4/setup.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from setuptools import find_packages, setup
|
|
4
|
+
|
|
5
|
+
# Read docs.md for long description
|
|
6
|
+
docs_path = Path(__file__).parent / "docs.md"
|
|
7
|
+
long_description = docs_path.read_text() if docs_path.exists() else ""
|
|
8
|
+
|
|
9
|
+
setup(
|
|
10
|
+
name="lam-cli",
|
|
11
|
+
version="0.1.4",
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
install_requires=[
|
|
14
|
+
"backoff>=2.2.1",
|
|
15
|
+
"certifi>=2024.12.14",
|
|
16
|
+
"charset-normalizer>=3.3.2",
|
|
17
|
+
"click>=8.1.7",
|
|
18
|
+
"idna>=3.7",
|
|
19
|
+
"logtail-python>=0.2.2",
|
|
20
|
+
"monotonic>=1.6",
|
|
21
|
+
"msgpack>=1.0.8",
|
|
22
|
+
"posthog>=3.4.0",
|
|
23
|
+
"psutil>=5.9.0",
|
|
24
|
+
"python-dateutil>=2.8.2",
|
|
25
|
+
"requests>=2.32.3",
|
|
26
|
+
"six>=1.16.0",
|
|
27
|
+
"urllib3>=2.2.2",
|
|
28
|
+
],
|
|
29
|
+
entry_points={
|
|
30
|
+
"console_scripts": [
|
|
31
|
+
"laminar=lam.lam:lam",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
python_requires=">=3.9",
|
|
35
|
+
license="GPLv3",
|
|
36
|
+
author="Laminar Run, Inc.",
|
|
37
|
+
author_email="connect@laminar.run",
|
|
38
|
+
description="Secure data transformation tool supporting JQ and JavaScript (Bun)",
|
|
39
|
+
long_description=long_description,
|
|
40
|
+
long_description_content_type="text/markdown",
|
|
41
|
+
url="https://github.com/laminar-run/lam",
|
|
42
|
+
project_urls={
|
|
43
|
+
"Documentation": "https://docs.laminar.run",
|
|
44
|
+
"Source": "https://github.com/laminar-run/lam",
|
|
45
|
+
"Issue Tracker": "https://github.com/laminar-run/lam/issues",
|
|
46
|
+
},
|
|
47
|
+
classifiers=[
|
|
48
|
+
"Development Status :: 4 - Beta",
|
|
49
|
+
"Environment :: Console",
|
|
50
|
+
"Intended Audience :: Developers",
|
|
51
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
52
|
+
"Operating System :: OS Independent",
|
|
53
|
+
"Programming Language :: Python :: 3",
|
|
54
|
+
"Programming Language :: Python :: 3.9",
|
|
55
|
+
"Programming Language :: Python :: 3.10",
|
|
56
|
+
"Programming Language :: Python :: 3.11",
|
|
57
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
58
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
59
|
+
"Topic :: Software Development :: Build Tools",
|
|
60
|
+
],
|
|
61
|
+
keywords="laminar, api, integration, transformation, json, jq, javascript, bun",
|
|
62
|
+
)
|
lam_cli-0.1.2/PKG-INFO
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: lam-cli
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: Secure data transformation tool supporting JQ and JavaScript (Bun)
|
|
5
|
-
Home-page: https://github.com/laminar-run/lam
|
|
6
|
-
Author: Laminar Run, Inc.
|
|
7
|
-
Author-email: connect@laminar.run
|
|
8
|
-
License: GPLv3
|
|
9
|
-
Project-URL: Documentation, https://docs.laminar.run
|
|
10
|
-
Project-URL: Source, https://github.com/laminar-run/lam
|
|
11
|
-
Project-URL: Issue Tracker, https://github.com/laminar-run/lam/issues
|
|
12
|
-
Keywords: laminar,api,integration,transformation,json,jq,javascript,bun
|
|
13
|
-
Classifier: Development Status :: 4 - Beta
|
|
14
|
-
Classifier: Environment :: Console
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
25
|
-
Requires-Python: >=3.9
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
Requires-Dist: backoff>=2.2.1
|
|
29
|
-
Requires-Dist: certifi>=2024.12.14
|
|
30
|
-
Requires-Dist: charset-normalizer>=3.3.2
|
|
31
|
-
Requires-Dist: click>=8.1.7
|
|
32
|
-
Requires-Dist: idna>=3.7
|
|
33
|
-
Requires-Dist: logtail-python>=0.2.2
|
|
34
|
-
Requires-Dist: monotonic>=1.6
|
|
35
|
-
Requires-Dist: msgpack>=1.0.8
|
|
36
|
-
Requires-Dist: posthog>=3.4.0
|
|
37
|
-
Requires-Dist: psutil>=5.9.0
|
|
38
|
-
Requires-Dist: python-dateutil>=2.8.2
|
|
39
|
-
Requires-Dist: requests>=2.32.3
|
|
40
|
-
Requires-Dist: six>=1.16.0
|
|
41
|
-
Requires-Dist: urllib3>=2.2.2
|
|
42
|
-
Dynamic: author
|
|
43
|
-
Dynamic: author-email
|
|
44
|
-
Dynamic: classifier
|
|
45
|
-
Dynamic: description
|
|
46
|
-
Dynamic: description-content-type
|
|
47
|
-
Dynamic: home-page
|
|
48
|
-
Dynamic: keywords
|
|
49
|
-
Dynamic: license
|
|
50
|
-
Dynamic: project-url
|
|
51
|
-
Dynamic: requires-dist
|
|
52
|
-
Dynamic: requires-python
|
|
53
|
-
Dynamic: summary
|
|
54
|
-
|
|
55
|
-
# lam
|
|
56
|
-
Lam is a data transformation tool for Laminar that supports both `jq` and JavaScript transformations using Bun.
|
|
57
|
-
|
|
58
|
-
## Quickstart
|
|
59
|
-
Install the dependencies:
|
|
60
|
-
```bash
|
|
61
|
-
# For JQ support
|
|
62
|
-
brew install jq # or sudo apt-get install jq
|
|
63
|
-
|
|
64
|
-
# For JavaScript support
|
|
65
|
-
curl -fsSL https://bun.sh/install | bash
|
|
66
|
-
|
|
67
|
-
make setup
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Run the CLI tool:
|
|
71
|
-
```bash
|
|
72
|
-
make cli ARGS="run <program> <input> [--language jq|js]"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Features
|
|
76
|
-
- JQ transformations (default)
|
|
77
|
-
- JavaScript transformations with Bun runtime
|
|
78
|
-
- Built-in utilities (lodash, date-fns)
|
|
79
|
-
- Resource monitoring and limits
|
|
80
|
-
- Detailed execution statistics
|
|
81
|
-
- Secure execution environment
|
|
82
|
-
|
|
83
|
-
## Examples
|
|
84
|
-
|
|
85
|
-
### JQ Transform
|
|
86
|
-
```bash
|
|
87
|
-
make cli ARGS="run examples/transform.jq data.json"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### JavaScript Transform
|
|
91
|
-
```bash
|
|
92
|
-
make cli ARGS="run examples/transform.js data.json --language js"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Example JavaScript transform:
|
|
96
|
-
```javascript
|
|
97
|
-
(input) => {
|
|
98
|
-
// Lodash available as _
|
|
99
|
-
return _.map(input.data, item => ({
|
|
100
|
-
value: item.value * 2
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Installation
|
|
106
|
-
|
|
107
|
-
### Docker Installation
|
|
108
|
-
```dockerfile
|
|
109
|
-
# Install lam-cli
|
|
110
|
-
RUN pip3 install git+https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/user/project.git@{version}
|
|
111
|
-
|
|
112
|
-
# Install dependencies
|
|
113
|
-
RUN apt-get update && apt-get install -y jq
|
|
114
|
-
RUN curl -fsSL https://bun.sh/install | bash
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Manual Setup
|
|
118
|
-
Create a virtual environment and install dependencies:
|
|
119
|
-
```bash
|
|
120
|
-
python3 -m venv ./venv
|
|
121
|
-
source ./venv/bin/activate
|
|
122
|
-
pip install -r requirements.txt
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Usage
|
|
126
|
-
```bash
|
|
127
|
-
# Basic usage
|
|
128
|
-
python3 ./lam/lam.py run <program> <input>
|
|
129
|
-
|
|
130
|
-
# With JavaScript
|
|
131
|
-
python3 ./lam/lam.py run script.js data.json --language js
|
|
132
|
-
|
|
133
|
-
# Full options
|
|
134
|
-
python3 ./lam/lam.py run <program> <input> \
|
|
135
|
-
--language [jq|js] \
|
|
136
|
-
--workspace_id <id> \
|
|
137
|
-
--flow_id <id> \
|
|
138
|
-
--execution_id <id> \
|
|
139
|
-
[--as-json]
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Resource Limits
|
|
143
|
-
- Maximum input size: 10MB
|
|
144
|
-
- Execution timeout: 5 seconds
|
|
145
|
-
- Memory limits enabled
|
|
146
|
-
- Disk space monitoring
|
|
147
|
-
|
|
148
|
-
## Security
|
|
149
|
-
- Sandboxed JavaScript execution
|
|
150
|
-
- Network access disabled
|
|
151
|
-
- Limited global scope
|
|
152
|
-
- Resource monitoring
|
|
153
|
-
- Secure dependency management
|
|
154
|
-
|
|
155
|
-
## Logging and Monitoring
|
|
156
|
-
- Execution statistics (duration, memory usage)
|
|
157
|
-
- Detailed error tracking
|
|
158
|
-
- PostHog analytics integration
|
|
159
|
-
- Log file generation
|
|
160
|
-
|
|
161
|
-
## Development
|
|
162
|
-
```bash
|
|
163
|
-
# Run all tests
|
|
164
|
-
make test
|
|
165
|
-
|
|
166
|
-
# Run specific test suite
|
|
167
|
-
make test-jq
|
|
168
|
-
make test-js
|
|
169
|
-
make test-js-edge-cases
|
|
170
|
-
|
|
171
|
-
# Run single test
|
|
172
|
-
make test-single TEST=test/js/example.js DATA=test/data/input.json
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Releases
|
|
176
|
-
Update version in `setup.py`:
|
|
177
|
-
```python
|
|
178
|
-
setup(
|
|
179
|
-
name="lam-cli",
|
|
180
|
-
version="0.0.<x>",
|
|
181
|
-
...
|
|
182
|
-
)
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Create and push tag:
|
|
186
|
-
```bash
|
|
187
|
-
git tag v<version>-<increment>
|
|
188
|
-
git push origin v<version>-<increment>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Dependencies
|
|
192
|
-
Update dependencies:
|
|
193
|
-
```bash
|
|
194
|
-
pip3 install <package>
|
|
195
|
-
pip3 freeze > requirements.txt
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Install Locally
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
pip3 install -e .
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Troubleshooting
|
|
205
|
-
|
|
206
|
-
### Package Installation Issues
|
|
207
|
-
If you encounter issues installing the package with pip, particularly with the certifi dependency, you may see errors like:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
error: uninstall-no-record-file
|
|
211
|
-
× Cannot uninstall certifi None
|
|
212
|
-
╰─> The package's contents are unknown: no RECORD file was found for certifi.
|
|
213
|
-
```
|
|
214
|
-
This can happen when multiple Python versions are installed or when system packages have been modified. Try these steps:
|
|
215
|
-
|
|
216
|
-
First, identify which Python version you're using:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
which python3
|
|
220
|
-
python3 --version
|
|
221
|
-
which pip3
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Remove the problematic certifi installation (adjust path based on your Python version):
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
sudo rm -rf /usr/local/lib/python3.13/site-packages/certifi
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Install certifi directly:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
pip3 install --ignore-installed certifi --break-system-packages
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Then try installing lam-cli again:
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
pip3 install . --break-system-packages
|
|
240
|
-
```
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: lam-cli
|
|
3
|
-
Version: 0.1.2
|
|
4
|
-
Summary: Secure data transformation tool supporting JQ and JavaScript (Bun)
|
|
5
|
-
Home-page: https://github.com/laminar-run/lam
|
|
6
|
-
Author: Laminar Run, Inc.
|
|
7
|
-
Author-email: connect@laminar.run
|
|
8
|
-
License: GPLv3
|
|
9
|
-
Project-URL: Documentation, https://docs.laminar.run
|
|
10
|
-
Project-URL: Source, https://github.com/laminar-run/lam
|
|
11
|
-
Project-URL: Issue Tracker, https://github.com/laminar-run/lam/issues
|
|
12
|
-
Keywords: laminar,api,integration,transformation,json,jq,javascript,bun
|
|
13
|
-
Classifier: Development Status :: 4 - Beta
|
|
14
|
-
Classifier: Environment :: Console
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
-
Classifier: Operating System :: OS Independent
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
25
|
-
Requires-Python: >=3.9
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
Requires-Dist: backoff>=2.2.1
|
|
29
|
-
Requires-Dist: certifi>=2024.12.14
|
|
30
|
-
Requires-Dist: charset-normalizer>=3.3.2
|
|
31
|
-
Requires-Dist: click>=8.1.7
|
|
32
|
-
Requires-Dist: idna>=3.7
|
|
33
|
-
Requires-Dist: logtail-python>=0.2.2
|
|
34
|
-
Requires-Dist: monotonic>=1.6
|
|
35
|
-
Requires-Dist: msgpack>=1.0.8
|
|
36
|
-
Requires-Dist: posthog>=3.4.0
|
|
37
|
-
Requires-Dist: psutil>=5.9.0
|
|
38
|
-
Requires-Dist: python-dateutil>=2.8.2
|
|
39
|
-
Requires-Dist: requests>=2.32.3
|
|
40
|
-
Requires-Dist: six>=1.16.0
|
|
41
|
-
Requires-Dist: urllib3>=2.2.2
|
|
42
|
-
Dynamic: author
|
|
43
|
-
Dynamic: author-email
|
|
44
|
-
Dynamic: classifier
|
|
45
|
-
Dynamic: description
|
|
46
|
-
Dynamic: description-content-type
|
|
47
|
-
Dynamic: home-page
|
|
48
|
-
Dynamic: keywords
|
|
49
|
-
Dynamic: license
|
|
50
|
-
Dynamic: project-url
|
|
51
|
-
Dynamic: requires-dist
|
|
52
|
-
Dynamic: requires-python
|
|
53
|
-
Dynamic: summary
|
|
54
|
-
|
|
55
|
-
# lam
|
|
56
|
-
Lam is a data transformation tool for Laminar that supports both `jq` and JavaScript transformations using Bun.
|
|
57
|
-
|
|
58
|
-
## Quickstart
|
|
59
|
-
Install the dependencies:
|
|
60
|
-
```bash
|
|
61
|
-
# For JQ support
|
|
62
|
-
brew install jq # or sudo apt-get install jq
|
|
63
|
-
|
|
64
|
-
# For JavaScript support
|
|
65
|
-
curl -fsSL https://bun.sh/install | bash
|
|
66
|
-
|
|
67
|
-
make setup
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Run the CLI tool:
|
|
71
|
-
```bash
|
|
72
|
-
make cli ARGS="run <program> <input> [--language jq|js]"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Features
|
|
76
|
-
- JQ transformations (default)
|
|
77
|
-
- JavaScript transformations with Bun runtime
|
|
78
|
-
- Built-in utilities (lodash, date-fns)
|
|
79
|
-
- Resource monitoring and limits
|
|
80
|
-
- Detailed execution statistics
|
|
81
|
-
- Secure execution environment
|
|
82
|
-
|
|
83
|
-
## Examples
|
|
84
|
-
|
|
85
|
-
### JQ Transform
|
|
86
|
-
```bash
|
|
87
|
-
make cli ARGS="run examples/transform.jq data.json"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### JavaScript Transform
|
|
91
|
-
```bash
|
|
92
|
-
make cli ARGS="run examples/transform.js data.json --language js"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Example JavaScript transform:
|
|
96
|
-
```javascript
|
|
97
|
-
(input) => {
|
|
98
|
-
// Lodash available as _
|
|
99
|
-
return _.map(input.data, item => ({
|
|
100
|
-
value: item.value * 2
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Installation
|
|
106
|
-
|
|
107
|
-
### Docker Installation
|
|
108
|
-
```dockerfile
|
|
109
|
-
# Install lam-cli
|
|
110
|
-
RUN pip3 install git+https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/user/project.git@{version}
|
|
111
|
-
|
|
112
|
-
# Install dependencies
|
|
113
|
-
RUN apt-get update && apt-get install -y jq
|
|
114
|
-
RUN curl -fsSL https://bun.sh/install | bash
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Manual Setup
|
|
118
|
-
Create a virtual environment and install dependencies:
|
|
119
|
-
```bash
|
|
120
|
-
python3 -m venv ./venv
|
|
121
|
-
source ./venv/bin/activate
|
|
122
|
-
pip install -r requirements.txt
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Usage
|
|
126
|
-
```bash
|
|
127
|
-
# Basic usage
|
|
128
|
-
python3 ./lam/lam.py run <program> <input>
|
|
129
|
-
|
|
130
|
-
# With JavaScript
|
|
131
|
-
python3 ./lam/lam.py run script.js data.json --language js
|
|
132
|
-
|
|
133
|
-
# Full options
|
|
134
|
-
python3 ./lam/lam.py run <program> <input> \
|
|
135
|
-
--language [jq|js] \
|
|
136
|
-
--workspace_id <id> \
|
|
137
|
-
--flow_id <id> \
|
|
138
|
-
--execution_id <id> \
|
|
139
|
-
[--as-json]
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Resource Limits
|
|
143
|
-
- Maximum input size: 10MB
|
|
144
|
-
- Execution timeout: 5 seconds
|
|
145
|
-
- Memory limits enabled
|
|
146
|
-
- Disk space monitoring
|
|
147
|
-
|
|
148
|
-
## Security
|
|
149
|
-
- Sandboxed JavaScript execution
|
|
150
|
-
- Network access disabled
|
|
151
|
-
- Limited global scope
|
|
152
|
-
- Resource monitoring
|
|
153
|
-
- Secure dependency management
|
|
154
|
-
|
|
155
|
-
## Logging and Monitoring
|
|
156
|
-
- Execution statistics (duration, memory usage)
|
|
157
|
-
- Detailed error tracking
|
|
158
|
-
- PostHog analytics integration
|
|
159
|
-
- Log file generation
|
|
160
|
-
|
|
161
|
-
## Development
|
|
162
|
-
```bash
|
|
163
|
-
# Run all tests
|
|
164
|
-
make test
|
|
165
|
-
|
|
166
|
-
# Run specific test suite
|
|
167
|
-
make test-jq
|
|
168
|
-
make test-js
|
|
169
|
-
make test-js-edge-cases
|
|
170
|
-
|
|
171
|
-
# Run single test
|
|
172
|
-
make test-single TEST=test/js/example.js DATA=test/data/input.json
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Releases
|
|
176
|
-
Update version in `setup.py`:
|
|
177
|
-
```python
|
|
178
|
-
setup(
|
|
179
|
-
name="lam-cli",
|
|
180
|
-
version="0.0.<x>",
|
|
181
|
-
...
|
|
182
|
-
)
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Create and push tag:
|
|
186
|
-
```bash
|
|
187
|
-
git tag v<version>-<increment>
|
|
188
|
-
git push origin v<version>-<increment>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Dependencies
|
|
192
|
-
Update dependencies:
|
|
193
|
-
```bash
|
|
194
|
-
pip3 install <package>
|
|
195
|
-
pip3 freeze > requirements.txt
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Install Locally
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
pip3 install -e .
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
## Troubleshooting
|
|
205
|
-
|
|
206
|
-
### Package Installation Issues
|
|
207
|
-
If you encounter issues installing the package with pip, particularly with the certifi dependency, you may see errors like:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
error: uninstall-no-record-file
|
|
211
|
-
× Cannot uninstall certifi None
|
|
212
|
-
╰─> The package's contents are unknown: no RECORD file was found for certifi.
|
|
213
|
-
```
|
|
214
|
-
This can happen when multiple Python versions are installed or when system packages have been modified. Try these steps:
|
|
215
|
-
|
|
216
|
-
First, identify which Python version you're using:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
which python3
|
|
220
|
-
python3 --version
|
|
221
|
-
which pip3
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Remove the problematic certifi installation (adjust path based on your Python version):
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
sudo rm -rf /usr/local/lib/python3.13/site-packages/certifi
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Install certifi directly:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
pip3 install --ignore-installed certifi --break-system-packages
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Then try installing lam-cli again:
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
pip3 install . --break-system-packages
|
|
240
|
-
```
|
lam_cli-0.1.2/setup.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from setuptools import find_packages, setup
|
|
4
|
-
|
|
5
|
-
# Read the README for the long description
|
|
6
|
-
this_directory = Path(__file__).parent
|
|
7
|
-
long_description = (this_directory / "README.md").read_text()
|
|
8
|
-
|
|
9
|
-
setup(
|
|
10
|
-
name='lam-cli',
|
|
11
|
-
version='0.1.2',
|
|
12
|
-
packages=find_packages(),
|
|
13
|
-
install_requires=[
|
|
14
|
-
'backoff>=2.2.1',
|
|
15
|
-
'certifi>=2024.12.14',
|
|
16
|
-
'charset-normalizer>=3.3.2',
|
|
17
|
-
'click>=8.1.7',
|
|
18
|
-
'idna>=3.7',
|
|
19
|
-
'logtail-python>=0.2.2',
|
|
20
|
-
'monotonic>=1.6',
|
|
21
|
-
'msgpack>=1.0.8',
|
|
22
|
-
'posthog>=3.4.0',
|
|
23
|
-
'psutil>=5.9.0',
|
|
24
|
-
'python-dateutil>=2.8.2',
|
|
25
|
-
'requests>=2.32.3',
|
|
26
|
-
'six>=1.16.0',
|
|
27
|
-
'urllib3>=2.2.2'
|
|
28
|
-
],
|
|
29
|
-
entry_points={
|
|
30
|
-
'console_scripts': [
|
|
31
|
-
'laminar=lam.lam:lam',
|
|
32
|
-
],
|
|
33
|
-
},
|
|
34
|
-
python_requires='>=3.9',
|
|
35
|
-
license='GPLv3',
|
|
36
|
-
author='Laminar Run, Inc.',
|
|
37
|
-
author_email='connect@laminar.run',
|
|
38
|
-
description='Secure data transformation tool supporting JQ and JavaScript (Bun)',
|
|
39
|
-
long_description=long_description,
|
|
40
|
-
long_description_content_type='text/markdown',
|
|
41
|
-
url='https://github.com/laminar-run/lam',
|
|
42
|
-
project_urls={
|
|
43
|
-
'Documentation': 'https://docs.laminar.run',
|
|
44
|
-
'Source': 'https://github.com/laminar-run/lam',
|
|
45
|
-
'Issue Tracker': 'https://github.com/laminar-run/lam/issues',
|
|
46
|
-
},
|
|
47
|
-
classifiers=[
|
|
48
|
-
'Development Status :: 4 - Beta',
|
|
49
|
-
'Environment :: Console',
|
|
50
|
-
'Intended Audience :: Developers',
|
|
51
|
-
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
|
52
|
-
'Operating System :: OS Independent',
|
|
53
|
-
'Programming Language :: Python :: 3',
|
|
54
|
-
'Programming Language :: Python :: 3.9',
|
|
55
|
-
'Programming Language :: Python :: 3.10',
|
|
56
|
-
'Programming Language :: Python :: 3.11',
|
|
57
|
-
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
58
|
-
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
|
59
|
-
'Topic :: Software Development :: Build Tools',
|
|
60
|
-
],
|
|
61
|
-
keywords='laminar, api, integration, transformation, json, jq, javascript, bun',
|
|
62
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|