klongpy 0.6.8__tar.gz → 0.6.9__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 (84) hide show
  1. {klongpy-0.6.8/klongpy.egg-info → klongpy-0.6.9}/PKG-INFO +40 -4
  2. {klongpy-0.6.8 → klongpy-0.6.9}/README.md +26 -0
  3. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/core.py +5 -3
  4. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/dyads.py +14 -4
  5. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/help.kg +2 -2
  6. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/monads.py +11 -10
  7. klongpy-0.6.9/klongpy/repl.py +73 -0
  8. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/sys_fn.py +1 -1
  9. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/sys_fn_ipc.py +0 -1
  10. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/web/sys_fn_web.py +14 -2
  11. {klongpy-0.6.8 → klongpy-0.6.9/klongpy.egg-info}/PKG-INFO +40 -4
  12. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy.egg-info/SOURCES.txt +5 -0
  13. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy.egg-info/requires.txt +2 -2
  14. {klongpy-0.6.8 → klongpy-0.6.9}/setup.py +2 -2
  15. klongpy-0.6.9/tests/test_eval_monad_list.py +34 -0
  16. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_interop.py +0 -1
  17. klongpy-0.6.9/tests/test_kg_asarray.py +94 -0
  18. klongpy-0.6.9/tests/test_reshape_strings.py +33 -0
  19. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_suite.py +1 -0
  20. klongpy-0.6.9/tests/test_sys_fn_web.py +50 -0
  21. {klongpy-0.6.8 → klongpy-0.6.9}/LICENSE +0 -0
  22. {klongpy-0.6.8 → klongpy-0.6.9}/MANIFEST.in +0 -0
  23. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/__init__.py +0 -0
  24. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/adverbs.py +0 -0
  25. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/backend.py +0 -0
  26. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/__init__.py +0 -0
  27. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/df_cache.py +0 -0
  28. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/file_cache.py +0 -0
  29. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/helpers.py +0 -0
  30. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/sys_fn_db.py +0 -0
  31. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/db/sys_fn_kvs.py +0 -0
  32. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/interpreter.py +0 -0
  33. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/csv.kg +0 -0
  34. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/edt.kg +0 -0
  35. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/eigenv.kg +0 -0
  36. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/huffman.kg +0 -0
  37. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/math.kg +0 -0
  38. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/nstat.kg +0 -0
  39. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/print.kg +0 -0
  40. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/set.kg +0 -0
  41. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/spline.kg +0 -0
  42. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/time.kg +0 -0
  43. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/lib/util.kg +0 -0
  44. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/sys_fn_timer.py +0 -0
  45. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/sys_var.py +0 -0
  46. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/utils.py +0 -0
  47. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/web/__init__.py +0 -0
  48. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/ws/__init__.py +0 -0
  49. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy/ws/sys_fn_ws.py +0 -0
  50. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy.egg-info/dependency_links.txt +0 -0
  51. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy.egg-info/not-zip-safe +0 -0
  52. {klongpy-0.6.8 → klongpy-0.6.9}/klongpy.egg-info/top_level.txt +0 -0
  53. {klongpy-0.6.8 → klongpy-0.6.9}/scripts/kgpy +0 -0
  54. {klongpy-0.6.8 → klongpy-0.6.9}/setup.cfg +0 -0
  55. {klongpy-0.6.8 → klongpy-0.6.9}/tests/__init__.py +0 -0
  56. {klongpy-0.6.8 → klongpy-0.6.9}/tests/gen_join_over.py +0 -0
  57. {klongpy-0.6.8 → klongpy-0.6.9}/tests/gen_py_suite.py +0 -0
  58. {klongpy-0.6.8 → klongpy-0.6.9}/tests/gen_test_fn.py +0 -0
  59. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_async.py +0 -0
  60. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_avg.py +0 -0
  61. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_duckdb.py +0 -0
  62. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_gen.py +0 -0
  63. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_ipc_overhead.py +0 -0
  64. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_join.py +0 -0
  65. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_load.py +0 -0
  66. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_prog.py +0 -0
  67. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_serdes.py +0 -0
  68. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_sys_fn_db.py +0 -0
  69. {klongpy-0.6.8 → klongpy-0.6.9}/tests/perf_vector.py +0 -0
  70. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_accel.py +0 -0
  71. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_df_cache.py +0 -0
  72. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_examples.py +0 -0
  73. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_extra_suite.py +0 -0
  74. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_file_cache.py +0 -0
  75. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_kgtests.py +0 -0
  76. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_known_bugs.py +0 -0
  77. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_prog.py +0 -0
  78. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_suite_file.py +0 -0
  79. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_sys_fn.py +0 -0
  80. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_sys_fn_db.py +0 -0
  81. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_sys_fn_ipc.py +0 -0
  82. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_sys_fn_timer.py +0 -0
  83. {klongpy-0.6.8 → klongpy-0.6.9}/tests/test_util.py +0 -0
  84. {klongpy-0.6.8 → klongpy-0.6.9}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: klongpy
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: High-Performance Klong array language with rich Python integration.
5
5
  Author: Brian Guarraci
6
6
  License: MIT
@@ -32,15 +32,25 @@ Provides-Extra: web
32
32
  Requires-Dist: aiohttp==3.9.4; extra == "web"
33
33
  Provides-Extra: db
34
34
  Requires-Dist: pandas==2.2.2; extra == "db"
35
- Requires-Dist: duckdb==1.0.0; extra == "db"
35
+ Requires-Dist: duckdb==1.3.0; extra == "db"
36
36
  Provides-Extra: ws
37
37
  Requires-Dist: websockets==12.0; extra == "ws"
38
38
  Provides-Extra: full
39
39
  Requires-Dist: colorama==0.4.6; extra == "full"
40
40
  Requires-Dist: aiohttp==3.9.4; extra == "full"
41
41
  Requires-Dist: pandas==2.2.2; extra == "full"
42
- Requires-Dist: duckdb==1.0.0; extra == "full"
42
+ Requires-Dist: duckdb==1.3.0; extra == "full"
43
43
  Requires-Dist: websockets==12.0; extra == "full"
44
+ Dynamic: author
45
+ Dynamic: classifier
46
+ Dynamic: description
47
+ Dynamic: description-content-type
48
+ Dynamic: license
49
+ Dynamic: license-file
50
+ Dynamic: provides-extra
51
+ Dynamic: requires-dist
52
+ Dynamic: requires-python
53
+ Dynamic: summary
44
54
 
45
55
 
46
56
  ![Unit Tests](https://github.com/briangu/klongpy/workflows/Unit%20Tests/badge.svg)
@@ -332,6 +342,31 @@ $ curl http://localhost:8888
332
342
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
333
343
  ```
334
344
 
345
+ You can also spin up this server directly inside the REPL:
346
+
347
+ ```kgpy
348
+ ?> .py("klongpy.web")
349
+ ?> data::!10
350
+ ?> index::{x; "Hello, Klong World! ", data}
351
+ ?> get:::{}; get,"/",index
352
+ ?> post:::{}
353
+ ?> h::.web(8888;get;post)
354
+ ```
355
+
356
+ And from another terminal:
357
+
358
+ ```bash
359
+ $ curl http://localhost:8888
360
+ ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
361
+ ```
362
+
363
+ Stop the server with:
364
+
365
+ ```kgpy
366
+ ?> .webc(h)
367
+ 1
368
+ ```
369
+
335
370
  ## Conclusion
336
371
 
337
372
  These examples are designed to illustrate the "batteries included" approach, ease of use and diverse applications of KlongPy, making it a versatile choice for various programming needs.
@@ -388,6 +423,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
388
423
  * Infinite precision: The main difference in this implementation of Klong is the lack of infinite precision. By using NumPy we are restricted to doubles.
389
424
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
390
425
  * KlongPy aims to be more "batteries included" approach to modules and contains additional features such as IPC, Web service, Websockets, etc.
426
+ * For array operations, KlongPy matches the shape of array arguments differently. Compare the results of an expression like `[1 2]+[[3 4][5 6]]` which in Klong produces `[[4 5] [7 8]]` but in KlongPy produces `[[4 6] [6 8]]`.
391
427
 
392
428
  # Related
393
429
 
@@ -288,6 +288,31 @@ $ curl http://localhost:8888
288
288
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
289
289
  ```
290
290
 
291
+ You can also spin up this server directly inside the REPL:
292
+
293
+ ```kgpy
294
+ ?> .py("klongpy.web")
295
+ ?> data::!10
296
+ ?> index::{x; "Hello, Klong World! ", data}
297
+ ?> get:::{}; get,"/",index
298
+ ?> post:::{}
299
+ ?> h::.web(8888;get;post)
300
+ ```
301
+
302
+ And from another terminal:
303
+
304
+ ```bash
305
+ $ curl http://localhost:8888
306
+ ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
307
+ ```
308
+
309
+ Stop the server with:
310
+
311
+ ```kgpy
312
+ ?> .webc(h)
313
+ 1
314
+ ```
315
+
291
316
  ## Conclusion
292
317
 
293
318
  These examples are designed to illustrate the "batteries included" approach, ease of use and diverse applications of KlongPy, making it a versatile choice for various programming needs.
@@ -344,6 +369,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
344
369
  * Infinite precision: The main difference in this implementation of Klong is the lack of infinite precision. By using NumPy we are restricted to doubles.
345
370
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
346
371
  * KlongPy aims to be more "batteries included" approach to modules and contains additional features such as IPC, Web service, Websockets, etc.
372
+ * For array operations, KlongPy matches the shape of array arguments differently. Compare the results of an expression like `[1 2]+[[3 4][5 6]]` which in Klong produces `[[4 5] [7 8]]` but in KlongPy produces `[[4 6] [6 8]]`.
347
373
 
348
374
  # Related
349
375
 
@@ -259,17 +259,19 @@ def kg_asarray(a):
259
259
  arr : ndarray
260
260
  The converted input data as a NumPy array, where all elements and sub-arrays are also NumPy arrays.
261
261
  """
262
+ if isinstance(a, str):
263
+ return str_to_chr_arr(a)
262
264
  try:
263
265
  arr = np.asarray(a)
264
266
  if arr.dtype.kind not in ['O','i','f']:
265
267
  raise ValueError
266
268
  except (np.VisibleDeprecationWarning, ValueError):
267
269
  try:
268
- arr = np.asarray(a,dtype=object)
270
+ arr = np.asarray(a, dtype=object)
269
271
  except ValueError:
270
272
  arr = [x.tolist() if np.isarray(x) else x for x in a]
271
- arr = np.asarray(arr,dtype=object)
272
- arr = np.asarray([kg_asarray(x) if isinstance(x,list) else x for x in arr],dtype=object)
273
+ arr = np.asarray(arr, dtype=object)
274
+ arr = np.asarray([kg_asarray(x) if isinstance(x, list) else x for x in arr], dtype=object)
273
275
  return arr
274
276
 
275
277
 
@@ -181,7 +181,11 @@ def eval_dyad_at_index(klong, a, b):
181
181
  j = False
182
182
  else:
183
183
  r = a
184
- return "".join(r) if j else r
184
+ if j:
185
+ if np.isarray(r) and r.ndim > 1:
186
+ return np.asarray(["".join(x) for x in r], dtype=object)
187
+ return "".join(r)
188
+ return r
185
189
 
186
190
 
187
191
  def eval_dyad_define(klong, n, v):
@@ -401,9 +405,11 @@ def _e_dyad_format2(a, b):
401
405
  """
402
406
  Unravel the broadcasting of a and b and apply __e_dyad_format2
403
407
  """
408
+ if is_list(a) and is_list(b):
409
+ return kg_asarray([vec_fn2(x, y, _e_dyad_format2) for x, y in zip(to_list(a), to_list(b))])
404
410
  if np.isarray(a) and np.isarray(b):
405
- return np.asarray([vec_fn2(x,y,_e_dyad_format2) for x,y in zip(a,b)])
406
- return __e_dyad_format2(a,b)
411
+ return np.asarray([vec_fn2(x, y, _e_dyad_format2) for x, y in zip(a, b)])
412
+ return __e_dyad_format2(a, b)
407
413
 
408
414
  def eval_dyad_format2(a, b):
409
415
  """
@@ -832,7 +838,11 @@ def eval_dyad_reshape(a, b):
832
838
  r = np.concatenate((np.tile(b,ns), b[:a - b.shape[0]*ns[0]]))
833
839
  else:
834
840
  r = np.full((a,), b)
835
- return "".join(r) if j else r
841
+ if j:
842
+ if np.isarray(r) and r.ndim > 1:
843
+ return np.asarray(["".join(x) for x in r], dtype=object)
844
+ return "".join(r)
845
+ return r
836
846
 
837
847
 
838
848
  def eval_dyad_rotate(a, b):
@@ -30,8 +30,8 @@ op.db::[
30
30
  [" a:$b" "Form"
31
31
  "b=string; convert 'b' to an object of the same form (type) as 'a'"]
32
32
  [" $a" "Format" "convert 'a' to a string representing the value of 'a'"]
33
- [" a$b" "Format2"
34
- "a=real; Format 'b', pad with 'a' blanks or to align to x.y digits"]
33
+ [" a$b" "Format2"
34
+ "a=real|list; when both operands are lists, apply pairwise. Format 'b', pad with 'a' blanks or to align to x.y digits"]
35
35
  [" >a" "Grade-Down"
36
36
  "a=vector; vector of indices of elements of 'a' in ascending order"]
37
37
  [" <a" "Grade-Up"
@@ -74,7 +74,8 @@ def eval_monad_expand_where(a):
74
74
  &[0 1 0 1 0] --> [1 3]
75
75
 
76
76
  """
77
- return np.concatenate([np.zeros(x, dtype=int) + i for i,x in enumerate(a if is_list(a) else [a])])
77
+ arr = a if is_list(a) else [a]
78
+ return np.repeat(np.arange(len(arr)), arr)
78
79
 
79
80
 
80
81
  def eval_monad_first(a):
@@ -163,7 +164,7 @@ def eval_monad_grade_up(a):
163
164
  >[[1] [2] [3]] --> [2 1 0]
164
165
 
165
166
  """
166
- return kg_argsort(str_to_chr_arr(a) if isinstance(a,str) else a)
167
+ return kg_argsort(kg_asarray(a))
167
168
 
168
169
 
169
170
  def eval_monad_grade_down(a):
@@ -174,7 +175,7 @@ def eval_monad_grade_down(a):
174
175
  See [Grade-Up].
175
176
 
176
177
  """
177
- return kg_argsort(str_to_chr_arr(a) if isinstance(a,str) else a, descending=True)
178
+ return kg_argsort(kg_asarray(a), descending=True)
178
179
 
179
180
 
180
181
  def eval_monad_groupby(a):
@@ -194,12 +195,12 @@ def eval_monad_groupby(a):
194
195
  ="hello foo" --> [[0] [1] [2 3] [4 7 8] [5] [6]]
195
196
 
196
197
  """
197
- q = np.asarray(str_to_chr_arr(a) if isinstance(a, str) else a)
198
- if len(q) == 0:
199
- return q
200
- a = q.argsort()
201
- r = np.split(a, np.where(q[a][1:] != q[a][:-1])[0] + 1)
202
- return np.asarray(r, dtype=object)
198
+ arr = kg_asarray(a)
199
+ if arr.size == 0:
200
+ return arr
201
+ vals, inverse = np.unique(arr, return_inverse=True)
202
+ groups = [np.where(inverse == i)[0] for i in range(len(vals))]
203
+ return kg_asarray(groups)
203
204
 
204
205
 
205
206
  def eval_monad_list(a):
@@ -217,7 +218,7 @@ def eval_monad_list(a):
217
218
  if isinstance(a, KGChar):
218
219
  return str(a)
219
220
  if isinstance(a, KGSym):
220
- np.asarray([a],dtype=object) # np interpets ':foo" as ':fo"
221
+ return np.asarray([a],dtype=object) # np interprets ':foo" as ':fo"
221
222
  return np.asarray([a])
222
223
 
223
224
 
@@ -0,0 +1,73 @@
1
+ import asyncio
2
+ import threading
3
+ import time
4
+ import os
5
+ import importlib.resources
6
+
7
+ from . import KlongInterpreter
8
+ from .utils import CallbackEvent
9
+
10
+
11
+ def start_loop(loop: asyncio.AbstractEventLoop, stop_event: asyncio.Event) -> None:
12
+ asyncio.set_event_loop(loop)
13
+ loop.run_until_complete(stop_event.wait())
14
+
15
+
16
+ def setup_async_loop(debug: bool = False, slow_callback_duration: float = 86400.0):
17
+ loop = asyncio.new_event_loop()
18
+ loop.slow_callback_duration = slow_callback_duration
19
+ if debug:
20
+ loop.set_debug(True)
21
+ stop_event = asyncio.Event()
22
+ thread = threading.Thread(target=start_loop, args=(loop, stop_event), daemon=True)
23
+ thread.start()
24
+ return loop, thread, stop_event
25
+
26
+
27
+ def cleanup_async_loop(loop: asyncio.AbstractEventLoop, loop_thread: threading.Thread, stop_event: asyncio.Event, debug: bool = False, name: str | None = None) -> None:
28
+ if loop.is_closed():
29
+ return
30
+
31
+ loop.call_soon_threadsafe(stop_event.set)
32
+ loop_thread.join()
33
+
34
+ pending_tasks = asyncio.all_tasks(loop=loop)
35
+ if len(pending_tasks) > 0:
36
+ if name:
37
+ print(f"WARNING: pending tasks in {name} loop")
38
+ for task in pending_tasks:
39
+ loop.call_soon_threadsafe(task.cancel)
40
+ while len(asyncio.all_tasks(loop=loop)) > 0:
41
+ time.sleep(0)
42
+
43
+ loop.stop()
44
+
45
+ if not loop.is_closed():
46
+ loop.close()
47
+
48
+
49
+ def append_pkg_resource_path_KLONGPATH() -> None:
50
+ with importlib.resources.as_file(importlib.resources.files('klongpy')) as pkg_path:
51
+ pkg_lib_path = os.path.join(pkg_path, 'lib')
52
+ klongpath = os.environ.get('KLONGPATH', '.:lib')
53
+ klongpath = f"{klongpath}:{pkg_lib_path}" if klongpath else str(pkg_lib_path)
54
+ os.environ['KLONGPATH'] = klongpath
55
+
56
+
57
+ def create_repl(debug: bool = False):
58
+ io_loop, io_thread, io_stop = setup_async_loop(debug=debug)
59
+ klong_loop, klong_thread, klong_stop = setup_async_loop(debug=debug)
60
+
61
+ append_pkg_resource_path_KLONGPATH()
62
+
63
+ klong = KlongInterpreter()
64
+ shutdown_event = CallbackEvent()
65
+ klong['.system'] = {'ioloop': io_loop, 'klongloop': klong_loop, 'closeEvent': shutdown_event}
66
+
67
+ return klong, (io_loop, io_thread, io_stop, klong_loop, klong_thread, klong_stop)
68
+
69
+
70
+ def cleanup_repl(loops, debug: bool = False) -> None:
71
+ io_loop, io_thread, io_stop, klong_loop, klong_thread, klong_stop = loops
72
+ cleanup_async_loop(io_loop, io_thread, io_stop, debug=debug, name='io_loop')
73
+ cleanup_async_loop(klong_loop, klong_thread, klong_stop, debug=debug, name='klong_loop')
@@ -405,7 +405,7 @@ def _import_module(klong, x, from_set=None):
405
405
  module = import_module_from_sys(x)
406
406
 
407
407
  export_items = module.__dict__.get("klongpy_exports") or module.__dict__
408
- ffn = lambda p: p[0] in from_set if from_set is not None else lambda p: not p[0].startswith("__")
408
+ ffn = (lambda p: p[0] in from_set) if from_set is not None else (lambda p: not p[0].startswith("__"))
409
409
 
410
410
  ctx = klong._context.pop()
411
411
  try:
@@ -919,7 +919,6 @@ def eval_sys_fn_create_ipc_server(klong, x):
919
919
  if "x" is 0, then the server is closed and existing client connections are dropped.
920
920
 
921
921
  """
922
- global _ipc_tcp_server
923
922
  x = str(x)
924
923
  parts = x.split(":")
925
924
  bind = parts[0] if len(parts) > 1 else None
@@ -1,5 +1,7 @@
1
1
  import logging
2
2
  import sys
3
+ import asyncio
4
+ import concurrent.futures
3
5
 
4
6
  from aiohttp import web
5
7
 
@@ -113,7 +115,17 @@ def eval_sys_fn_create_web_server(klong, x, y, z):
113
115
  site = web.TCPSite(runner, bind, port)
114
116
  await site.start()
115
117
 
116
- server_task = klong['.system']['ioloop'].create_task(start_server())
118
+ # create the server task in the ioloop thread and capture the task handle
119
+ server_loop = klong['.system']['ioloop']
120
+ task_future = concurrent.futures.Future()
121
+
122
+ def _start():
123
+ task = asyncio.create_task(start_server())
124
+ task_future.set_result(task)
125
+
126
+ server_loop.call_soon_threadsafe(_start)
127
+ server_task = task_future.result()
128
+
117
129
  return WebServerHandle(bind, port, runner, server_task)
118
130
 
119
131
 
@@ -129,7 +141,7 @@ def eval_sys_fn_shutdown_web_server(klong, x):
129
141
  x = x.a.fn
130
142
  if isinstance(x, WebServerHandle) and x.runner is not None:
131
143
  print("shutting down web server")
132
- klong['.system']['ioloop'].run_until_complete(x.shutdown())
144
+ asyncio.run_coroutine_threadsafe(x.shutdown(), klong['.system']['ioloop']).result()
133
145
  return 1
134
146
  return 0
135
147
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: klongpy
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: High-Performance Klong array language with rich Python integration.
5
5
  Author: Brian Guarraci
6
6
  License: MIT
@@ -32,15 +32,25 @@ Provides-Extra: web
32
32
  Requires-Dist: aiohttp==3.9.4; extra == "web"
33
33
  Provides-Extra: db
34
34
  Requires-Dist: pandas==2.2.2; extra == "db"
35
- Requires-Dist: duckdb==1.0.0; extra == "db"
35
+ Requires-Dist: duckdb==1.3.0; extra == "db"
36
36
  Provides-Extra: ws
37
37
  Requires-Dist: websockets==12.0; extra == "ws"
38
38
  Provides-Extra: full
39
39
  Requires-Dist: colorama==0.4.6; extra == "full"
40
40
  Requires-Dist: aiohttp==3.9.4; extra == "full"
41
41
  Requires-Dist: pandas==2.2.2; extra == "full"
42
- Requires-Dist: duckdb==1.0.0; extra == "full"
42
+ Requires-Dist: duckdb==1.3.0; extra == "full"
43
43
  Requires-Dist: websockets==12.0; extra == "full"
44
+ Dynamic: author
45
+ Dynamic: classifier
46
+ Dynamic: description
47
+ Dynamic: description-content-type
48
+ Dynamic: license
49
+ Dynamic: license-file
50
+ Dynamic: provides-extra
51
+ Dynamic: requires-dist
52
+ Dynamic: requires-python
53
+ Dynamic: summary
44
54
 
45
55
 
46
56
  ![Unit Tests](https://github.com/briangu/klongpy/workflows/Unit%20Tests/badge.svg)
@@ -332,6 +342,31 @@ $ curl http://localhost:8888
332
342
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
333
343
  ```
334
344
 
345
+ You can also spin up this server directly inside the REPL:
346
+
347
+ ```kgpy
348
+ ?> .py("klongpy.web")
349
+ ?> data::!10
350
+ ?> index::{x; "Hello, Klong World! ", data}
351
+ ?> get:::{}; get,"/",index
352
+ ?> post:::{}
353
+ ?> h::.web(8888;get;post)
354
+ ```
355
+
356
+ And from another terminal:
357
+
358
+ ```bash
359
+ $ curl http://localhost:8888
360
+ ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
361
+ ```
362
+
363
+ Stop the server with:
364
+
365
+ ```kgpy
366
+ ?> .webc(h)
367
+ 1
368
+ ```
369
+
335
370
  ## Conclusion
336
371
 
337
372
  These examples are designed to illustrate the "batteries included" approach, ease of use and diverse applications of KlongPy, making it a versatile choice for various programming needs.
@@ -388,6 +423,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
388
423
  * Infinite precision: The main difference in this implementation of Klong is the lack of infinite precision. By using NumPy we are restricted to doubles.
389
424
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
390
425
  * KlongPy aims to be more "batteries included" approach to modules and contains additional features such as IPC, Web service, Websockets, etc.
426
+ * For array operations, KlongPy matches the shape of array arguments differently. Compare the results of an expression like `[1 2]+[[3 4][5 6]]` which in Klong produces `[[4 5] [7 8]]` but in KlongPy produces `[[4 6] [6 8]]`.
391
427
 
392
428
  # Related
393
429
 
@@ -9,6 +9,7 @@ klongpy/core.py
9
9
  klongpy/dyads.py
10
10
  klongpy/interpreter.py
11
11
  klongpy/monads.py
12
+ klongpy/repl.py
12
13
  klongpy/sys_fn.py
13
14
  klongpy/sys_fn_ipc.py
14
15
  klongpy/sys_fn_timer.py
@@ -60,18 +61,22 @@ tests/perf_sys_fn_db.py
60
61
  tests/perf_vector.py
61
62
  tests/test_accel.py
62
63
  tests/test_df_cache.py
64
+ tests/test_eval_monad_list.py
63
65
  tests/test_examples.py
64
66
  tests/test_extra_suite.py
65
67
  tests/test_file_cache.py
66
68
  tests/test_interop.py
69
+ tests/test_kg_asarray.py
67
70
  tests/test_kgtests.py
68
71
  tests/test_known_bugs.py
69
72
  tests/test_prog.py
73
+ tests/test_reshape_strings.py
70
74
  tests/test_suite.py
71
75
  tests/test_suite_file.py
72
76
  tests/test_sys_fn.py
73
77
  tests/test_sys_fn_db.py
74
78
  tests/test_sys_fn_ipc.py
75
79
  tests/test_sys_fn_timer.py
80
+ tests/test_sys_fn_web.py
76
81
  tests/test_util.py
77
82
  tests/utils.py
@@ -20,13 +20,13 @@ cupy
20
20
 
21
21
  [db]
22
22
  pandas==2.2.2
23
- duckdb==1.0.0
23
+ duckdb==1.3.0
24
24
 
25
25
  [full]
26
26
  colorama==0.4.6
27
27
  aiohttp==3.9.4
28
28
  pandas==2.2.2
29
- duckdb==1.0.0
29
+ duckdb==1.3.0
30
30
  websockets==12.0
31
31
 
32
32
  [repl]
@@ -17,7 +17,7 @@ extra_requires = {
17
17
  'rocm-4-3': ["cupy-rocm-4-3"],
18
18
  'repl': ["colorama==0.4.6"],
19
19
  'web': ["aiohttp==3.9.4"],
20
- 'db': ["pandas==2.2.2","duckdb==1.0.0"],
20
+ 'db': ["pandas==2.2.2","duckdb==1.3.0"],
21
21
  'ws': ["websockets==12.0"],
22
22
  }
23
23
 
@@ -27,7 +27,7 @@ extra_requires['full'] = extra_requires['repl'] + extra_requires['web'] + extra_
27
27
  setup(
28
28
  name='klongpy',
29
29
  packages=find_packages(),
30
- version='0.6.8',
30
+ version='0.6.9',
31
31
  description='High-Performance Klong array language with rich Python integration.',
32
32
  author='Brian Guarraci',
33
33
  license='MIT',
@@ -0,0 +1,34 @@
1
+ import unittest
2
+ import numpy as np
3
+
4
+ from klongpy import KlongInterpreter
5
+ from klongpy.core import KGSym
6
+ from tests.utils import kg_equal
7
+
8
+
9
+ class TestEvalMonadList(unittest.TestCase):
10
+
11
+ def setUp(self):
12
+ self.klong = KlongInterpreter()
13
+
14
+ def test_int(self):
15
+ r = self.klong(',1')
16
+ self.assertTrue(kg_equal(r, np.asarray([1])))
17
+
18
+ def test_symbol(self):
19
+ r = self.klong(',:foo')
20
+ self.assertTrue(r.dtype == object)
21
+ self.assertEqual(len(r), 1)
22
+ self.assertEqual(r[0], KGSym('foo'))
23
+
24
+ def test_string(self):
25
+ r = self.klong(',"xyz"')
26
+ self.assertTrue(kg_equal(r, np.asarray(['xyz'], dtype=object)))
27
+
28
+ def test_list(self):
29
+ r = self.klong(',[1]')
30
+ self.assertTrue(kg_equal(r, np.asarray([[1]], dtype=object)))
31
+
32
+
33
+ if __name__ == '__main__':
34
+ unittest.main()
@@ -5,7 +5,6 @@ from datetime import datetime
5
5
  from utils import *
6
6
 
7
7
  from klongpy import KlongInterpreter
8
- from klongpy.core import KGChar
9
8
 
10
9
 
11
10
  class TestPythonInterop(unittest.TestCase):
@@ -0,0 +1,94 @@
1
+ import unittest
2
+
3
+ import numpy as np
4
+
5
+ from klongpy.core import kg_asarray, KGChar, KGSym
6
+
7
+
8
+ class TestKGAsArray(unittest.TestCase):
9
+
10
+ def test_kg_asarray_scalar_int(self):
11
+ x = 42
12
+ arr = kg_asarray(x)
13
+ assert np.isarray(arr)
14
+ assert arr.dtype == int
15
+ assert arr.shape == ()
16
+ assert arr.item() == 42
17
+
18
+ def test_kg_asarray_scalar_float(self):
19
+ x = 3.14
20
+ arr = kg_asarray(x)
21
+ assert np.isarray(arr)
22
+ assert arr.dtype == float
23
+ assert arr.item() == 3.14
24
+
25
+ def test_kg_asarray_empty_list(self):
26
+ x = []
27
+ arr = kg_asarray(x)
28
+ assert np.isarray(arr)
29
+ assert arr.dtype == float # default dtype for empty list
30
+ assert arr.size == 0
31
+
32
+ def test_kg_asarray_string(self):
33
+ s = "hello"
34
+ arr = kg_asarray(s)
35
+ assert np.isarray(arr)
36
+ assert arr.dtype == object # assuming KGChar is object dtype
37
+ assert "".join(arr) == "hello"
38
+
39
+ def test_kg_asarray_list_of_ints(self):
40
+ x = [1, 2, 3]
41
+ arr = kg_asarray(x)
42
+ assert np.isarray(arr)
43
+ assert arr.dtype == int
44
+ assert np.array_equal(arr, [1, 2, 3])
45
+
46
+ def test_kg_asarray_nested_list_uniform(self):
47
+ x = [[1, 2], [3, 4]]
48
+ arr = kg_asarray(x)
49
+ assert np.isarray(arr)
50
+ assert arr.shape == (2,2)
51
+ assert arr.dtype == int
52
+ assert np.array_equal(arr, [[1,2],[3,4]])
53
+
54
+ @unittest.skip("what is the expected behavior for this case?")
55
+ def test_kg_asarray_nested_list_heterogeneous(self):
56
+ # should embedded strings be expanded to individual characters?
57
+ x = [[1, 2], "abc", [3.14, None]]
58
+ arr = kg_asarray(x)
59
+ assert np.isarray(arr)
60
+ # Because of heterogeneous data, dtype should be object.
61
+ assert arr.dtype == object
62
+ # Check that sub-elements are arrays
63
+ assert np.isarray(arr[0])
64
+ assert np.isarray(arr[1])
65
+ assert np.isarray(arr[2])
66
+
67
+ def test_kg_asarray_already_array(self):
68
+ x = np.array([1, 2, 3])
69
+ arr = kg_asarray(x)
70
+ # Should return as-is because already suitable dtype
71
+ assert arr is x
72
+
73
+ def test_kg_asarray_jagged_list(self):
74
+ x = [[1, 2, 3], [4, 5], [6]]
75
+ arr = kg_asarray(x)
76
+ assert np.isarray(arr)
77
+ # Jagged => object dtype
78
+ assert arr.dtype == object
79
+ # Each element should be an array
80
+ assert all(np.isarray(e) for e in arr)
81
+ assert np.array_equal(arr[0], [1, 2, 3])
82
+ assert np.array_equal(arr[1], [4, 5])
83
+ assert np.array_equal(arr[2], [6])
84
+
85
+
86
+ def benchmark_kg_asarray(self):
87
+ import timeit
88
+ x = [[1, 2], [3, 4]]
89
+ print(timeit.timeit(lambda: kg_asarray(x), number=100_000))
90
+
91
+
92
+ if __name__ == "__main__":
93
+ # run the benchmark
94
+ TestKGAsArray().benchmark_kg_asarray()
@@ -0,0 +1,33 @@
1
+ import unittest
2
+ import numpy as np
3
+
4
+ from klongpy import KlongInterpreter
5
+ from tests.utils import kg_equal
6
+
7
+ class TestReshapeStrings(unittest.TestCase):
8
+ def setUp(self):
9
+ self.klong = KlongInterpreter()
10
+
11
+ def test_reshape_string_len1(self):
12
+ r = self.klong('[2 2]:^"a"')
13
+ self.assertTrue(kg_equal(r, np.asarray(["aa", "aa"], dtype=object)))
14
+
15
+ def test_reshape_string_len2(self):
16
+ r = self.klong('[2 2]:^"ab"')
17
+ self.assertTrue(kg_equal(r, np.asarray(["ab", "ab"], dtype=object)))
18
+
19
+ def test_reshape_string_len3(self):
20
+ r = self.klong('[2 2]:^"abc"')
21
+ self.assertTrue(kg_equal(r, np.asarray(["ab", "ca"], dtype=object)))
22
+
23
+ def test_reshape_string_len4(self):
24
+ r = self.klong('[2 2]:^"abcd"')
25
+ self.assertTrue(kg_equal(r, np.asarray(["ab", "cd"], dtype=object)))
26
+
27
+ def test_reshape_string_larger_shape(self):
28
+ r = self.klong('[3 3]:^"abcd"')
29
+ self.assertTrue(kg_equal(r, np.asarray(["abc", "dab", "cda"], dtype=object)))
30
+
31
+
32
+ if __name__ == '__main__':
33
+ unittest.main()
@@ -484,6 +484,7 @@ class TestCoreSuite(unittest.TestCase):
484
484
  self.assert_eval_cmp('5$1.23', '"1.23 "')
485
485
  self.assert_eval_cmp('5$-1.23', '"-1.23"')
486
486
  self.assert_eval_cmp('6$-1.23', '"-1.23 "')
487
+ self.assert_eval_cmp('[1]$[1]', '["1"]')
487
488
  self.assert_eval_cmp('(-10)$0', '" 0"')
488
489
  self.assert_eval_cmp('(-10)$123', '" 123"')
489
490
  self.assert_eval_cmp('(-10)$-123', '" -123"')
@@ -0,0 +1,50 @@
1
+ import asyncio
2
+ import socket
3
+ import unittest
4
+
5
+ import aiohttp
6
+
7
+ from klongpy.repl import create_repl, cleanup_repl
8
+
9
+
10
+ class TestSysFnWeb(unittest.TestCase):
11
+ def setUp(self):
12
+ self.klong, self.loops = create_repl()
13
+ (self.ioloop, self.ioloop_thread, self.io_stop,
14
+ self.klongloop, self.klongloop_thread, self.klong_stop) = self.loops
15
+ self.handle = None
16
+
17
+ def tearDown(self):
18
+ if self.handle is not None and self.handle.task is not None:
19
+ asyncio.run_coroutine_threadsafe(self.handle.shutdown(), self.ioloop).result()
20
+ cleanup_repl(self.loops)
21
+
22
+ def _free_port(self):
23
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24
+ s.bind(("", 0))
25
+ port = s.getsockname()[1]
26
+ s.close()
27
+ return port
28
+
29
+ def test_web_server_start_and_stop(self):
30
+ klong = self.klong
31
+ port = self._free_port()
32
+
33
+ klong('.py("klongpy.web")')
34
+ klong('index::{x;"hello"}')
35
+ klong('get:::{}')
36
+ klong('get,"/",index')
37
+ klong('post:::{}')
38
+ handle = klong(f'h::.web({port};get;post)')
39
+ self.handle = handle
40
+
41
+ async def fetch():
42
+ async with aiohttp.ClientSession() as session:
43
+ async with session.get(f"http://localhost:{port}/") as resp:
44
+ return await resp.text()
45
+
46
+ response = asyncio.run_coroutine_threadsafe(fetch(), self.ioloop).result()
47
+ self.assertEqual(response, "hello")
48
+
49
+ asyncio.run_coroutine_threadsafe(handle.shutdown(), self.ioloop).result()
50
+
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
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
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
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
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes