klongpy 0.6.7__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.7/klongpy.egg-info → klongpy-0.6.9}/PKG-INFO +40 -6
  2. {klongpy-0.6.7 → klongpy-0.6.9}/README.md +26 -2
  3. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/core.py +16 -7
  4. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/dyads.py +14 -4
  5. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/interpreter.py +8 -4
  6. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/help.kg +2 -2
  7. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/monads.py +11 -10
  8. klongpy-0.6.9/klongpy/repl.py +73 -0
  9. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/sys_fn.py +3 -3
  10. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/sys_fn_ipc.py +0 -1
  11. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/web/sys_fn_web.py +14 -2
  12. {klongpy-0.6.7 → klongpy-0.6.9/klongpy.egg-info}/PKG-INFO +40 -6
  13. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy.egg-info/SOURCES.txt +5 -0
  14. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy.egg-info/requires.txt +2 -2
  15. {klongpy-0.6.7 → klongpy-0.6.9}/setup.py +2 -2
  16. klongpy-0.6.9/tests/test_eval_monad_list.py +34 -0
  17. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_interop.py +0 -1
  18. klongpy-0.6.9/tests/test_kg_asarray.py +94 -0
  19. klongpy-0.6.9/tests/test_reshape_strings.py +33 -0
  20. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_suite.py +1 -0
  21. klongpy-0.6.9/tests/test_sys_fn_web.py +50 -0
  22. {klongpy-0.6.7 → klongpy-0.6.9}/LICENSE +0 -0
  23. {klongpy-0.6.7 → klongpy-0.6.9}/MANIFEST.in +0 -0
  24. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/__init__.py +0 -0
  25. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/adverbs.py +0 -0
  26. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/backend.py +0 -0
  27. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/__init__.py +0 -0
  28. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/df_cache.py +0 -0
  29. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/file_cache.py +0 -0
  30. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/helpers.py +0 -0
  31. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/sys_fn_db.py +0 -0
  32. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/db/sys_fn_kvs.py +0 -0
  33. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/csv.kg +0 -0
  34. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/edt.kg +0 -0
  35. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/eigenv.kg +0 -0
  36. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/huffman.kg +0 -0
  37. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/math.kg +0 -0
  38. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/nstat.kg +0 -0
  39. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/print.kg +0 -0
  40. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/set.kg +0 -0
  41. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/spline.kg +0 -0
  42. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/time.kg +0 -0
  43. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/lib/util.kg +0 -0
  44. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/sys_fn_timer.py +0 -0
  45. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/sys_var.py +0 -0
  46. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/utils.py +0 -0
  47. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/web/__init__.py +0 -0
  48. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/ws/__init__.py +0 -0
  49. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy/ws/sys_fn_ws.py +0 -0
  50. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy.egg-info/dependency_links.txt +0 -0
  51. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy.egg-info/not-zip-safe +0 -0
  52. {klongpy-0.6.7 → klongpy-0.6.9}/klongpy.egg-info/top_level.txt +0 -0
  53. {klongpy-0.6.7 → klongpy-0.6.9}/scripts/kgpy +0 -0
  54. {klongpy-0.6.7 → klongpy-0.6.9}/setup.cfg +0 -0
  55. {klongpy-0.6.7 → klongpy-0.6.9}/tests/__init__.py +0 -0
  56. {klongpy-0.6.7 → klongpy-0.6.9}/tests/gen_join_over.py +0 -0
  57. {klongpy-0.6.7 → klongpy-0.6.9}/tests/gen_py_suite.py +0 -0
  58. {klongpy-0.6.7 → klongpy-0.6.9}/tests/gen_test_fn.py +0 -0
  59. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_async.py +0 -0
  60. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_avg.py +0 -0
  61. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_duckdb.py +0 -0
  62. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_gen.py +0 -0
  63. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_ipc_overhead.py +0 -0
  64. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_join.py +0 -0
  65. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_load.py +0 -0
  66. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_prog.py +0 -0
  67. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_serdes.py +0 -0
  68. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_sys_fn_db.py +0 -0
  69. {klongpy-0.6.7 → klongpy-0.6.9}/tests/perf_vector.py +0 -0
  70. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_accel.py +0 -0
  71. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_df_cache.py +0 -0
  72. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_examples.py +0 -0
  73. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_extra_suite.py +0 -0
  74. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_file_cache.py +0 -0
  75. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_kgtests.py +0 -0
  76. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_known_bugs.py +0 -0
  77. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_prog.py +0 -0
  78. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_suite_file.py +0 -0
  79. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_sys_fn.py +0 -0
  80. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_sys_fn_db.py +0 -0
  81. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_sys_fn_ipc.py +0 -0
  82. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_sys_fn_timer.py +0 -0
  83. {klongpy-0.6.7 → klongpy-0.6.9}/tests/test_util.py +0 -0
  84. {klongpy-0.6.7 → 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.7
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)
@@ -54,8 +64,6 @@ Requires-Dist: websockets==12.0; extra == "full"
54
64
  [![Downloads](https://static.pepy.tech/badge/klongpy/month)](https://pepy.tech/project/klongpy)
55
65
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
56
66
 
57
- [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/klongpy.svg?style=social&label=Follow%20%40KlongPy)](https://twitter.com/klongpy)
58
-
59
67
  # KlongPy: High-Performance Array Programming in Python
60
68
 
61
69
  KlongPy is a Python adaptation of the [Klong](https://t3x.org/klong) [array language](https://en.wikipedia.org/wiki/Array_programming), known for its high-performance vectorized operations that leverage the power of NumPy. Embracing a "batteries included" philosophy, KlongPy combines built-in modules with Python's expansive ecosystem, facilitating rapid application development with Klong's succinct syntax.
@@ -334,6 +342,31 @@ $ curl http://localhost:8888
334
342
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
335
343
  ```
336
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
+
337
370
  ## Conclusion
338
371
 
339
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.
@@ -390,6 +423,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
390
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.
391
424
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
392
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]]`.
393
427
 
394
428
  # Related
395
429
 
@@ -10,8 +10,6 @@
10
10
  [![Downloads](https://static.pepy.tech/badge/klongpy/month)](https://pepy.tech/project/klongpy)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
12
 
13
- [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/klongpy.svg?style=social&label=Follow%20%40KlongPy)](https://twitter.com/klongpy)
14
-
15
13
  # KlongPy: High-Performance Array Programming in Python
16
14
 
17
15
  KlongPy is a Python adaptation of the [Klong](https://t3x.org/klong) [array language](https://en.wikipedia.org/wiki/Array_programming), known for its high-performance vectorized operations that leverage the power of NumPy. Embracing a "batteries included" philosophy, KlongPy combines built-in modules with Python's expansive ecosystem, facilitating rapid application development with Klong's succinct syntax.
@@ -290,6 +288,31 @@ $ curl http://localhost:8888
290
288
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
291
289
  ```
292
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
+
293
316
  ## Conclusion
294
317
 
295
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.
@@ -346,6 +369,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
346
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.
347
370
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
348
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]]`.
349
373
 
350
374
  # Related
351
375
 
@@ -87,6 +87,12 @@ class KGCond(list):
87
87
  pass
88
88
 
89
89
 
90
+ def safe_inspect(fn, follow_wrapped=True):
91
+ try:
92
+ return inspect.signature(fn, follow_wrapped=follow_wrapped).parameters
93
+ except ValueError:
94
+ return {"args":[]}
95
+
90
96
  class KGLambda:
91
97
  """
92
98
  KGLambda wraps a lambda and make it available to Klong, allowing for direct
@@ -109,7 +115,7 @@ class KGLambda:
109
115
  """
110
116
  def __init__(self, fn, args=None, provide_klong=False, wildcard=False):
111
117
  self.fn = fn
112
- params = args or inspect.signature(self.fn, follow_wrapped=True).parameters
118
+ params = args or safe_inspect(fn)
113
119
  self.args = [reserved_fn_symbol_map[x] for x in reserved_fn_args if x in params]
114
120
  self._provide_klong = provide_klong or 'klong' in params
115
121
  self._wildcard = wildcard
@@ -253,17 +259,19 @@ def kg_asarray(a):
253
259
  arr : ndarray
254
260
  The converted input data as a NumPy array, where all elements and sub-arrays are also NumPy arrays.
255
261
  """
262
+ if isinstance(a, str):
263
+ return str_to_chr_arr(a)
256
264
  try:
257
265
  arr = np.asarray(a)
258
266
  if arr.dtype.kind not in ['O','i','f']:
259
267
  raise ValueError
260
268
  except (np.VisibleDeprecationWarning, ValueError):
261
269
  try:
262
- arr = np.asarray(a,dtype=object)
270
+ arr = np.asarray(a, dtype=object)
263
271
  except ValueError:
264
272
  arr = [x.tolist() if np.isarray(x) else x for x in a]
265
- arr = np.asarray(arr,dtype=object)
266
- 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)
267
275
  return arr
268
276
 
269
277
 
@@ -636,6 +644,7 @@ def read_cond(klong, t, i=0):
636
644
  i = cexpect(t, i, ';')
637
645
  i,n = klong._expr(t, i, ignore_newline=True)
638
646
  r.append(n)
647
+ i = skip(t,i,ignore_newline=True)
639
648
  i = cexpect(t, i, ']')
640
649
  return i, KGCond(r)
641
650
 
@@ -694,9 +703,9 @@ def kg_read(t, i=0, read_neg=False, ignore_newline=False, module=None, list_leve
694
703
  if i >= len(t):
695
704
  return i, None
696
705
  a = t[i]
697
- if a in ['\n', ';']:
698
- return i+1,';'
699
- elif a in ['(',')','{','}',']']:
706
+ if a == '\n':
707
+ a = ';' # convert newlines to semicolons
708
+ if a in [';','(',')','{','}',']']:
700
709
  return i+1,a
701
710
  elif cmatch2(t, i, '0', 'c'):
702
711
  return read_char(t, i)
@@ -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):
@@ -119,7 +119,7 @@ class KlongContext():
119
119
  if dk.startswith(tk):
120
120
  return True
121
121
  return False
122
-
122
+
123
123
  def __iter__(self):
124
124
  seen = set()
125
125
  for d in self._context:
@@ -358,6 +358,7 @@ class KlongInterpreter():
358
358
  if safe_eq(a, '{'): # read fn
359
359
  i,a = self.prog(t, i, ignore_newline=True)
360
360
  a = a[0] if len(a) == 1 else a
361
+ i = skip(t, i, ignore_newline=True)
361
362
  i = cexpect(t, i, '}')
362
363
  arity = get_fn_arity(a)
363
364
  if cmatch(t, i, '(') or cmatch2(t,i,':','('):
@@ -416,6 +417,7 @@ class KlongInterpreter():
416
417
  if safe_eq(aa, '{'): # read fn
417
418
  i,aa = self.prog(t, i, ignore_newline=True)
418
419
  aa = aa[0] if len(aa) == 1 else aa
420
+ i = skip(t, i, ignore_newline=True)
419
421
  i = cexpect(t, i, '}')
420
422
  arity = get_fn_arity(aa)
421
423
  if cmatch(t, i, '(') or cmatch2(t,i,':','('):
@@ -433,6 +435,8 @@ class KlongInterpreter():
433
435
  i, aaa = self._expr(t, i, ignore_newline=ignore_newline)
434
436
  a = KGFn(aa, [a, aaa], arity=2)
435
437
  ii, aa = kg_read(t, i, ignore_newline=ignore_newline, module=self.current_module())
438
+ if ignore_newline and safe_eq(a, '\n'):
439
+ i = skip(t, i, ignore_newline=True)
436
440
  return i, a
437
441
 
438
442
  def prog(self, t, i=0, ignore_newline=False):
@@ -481,12 +485,12 @@ class KlongInterpreter():
481
485
  - f_args is the list of arguments provided to the function during the current invocation.
482
486
  - f.args (if f is an instance of KGFn) represents predefined arguments associated with the function.
483
487
  These are arguments that are already set by virtue of the function being a projection of another function.
484
- - During the resolution process, if the function f is a KGFn with predefined arguments (projections),
488
+ - During the resolution process, if the function f is a KGFn with predefined arguments (projections),
485
489
  these arguments are appended to the f_args list.
486
- - The resolution process ensures that if there are placeholders in the predefined arguments, they are
490
+ - The resolution process ensures that if there are placeholders in the predefined arguments, they are
487
491
  filled in with values from the provided arguments. However, if the function itself is being projected,
488
492
  f_args can still contain a None indicating an empty projection slot.
489
- - By the end of the resolution, f_args contains the arguments that will be passed to the function for
493
+ - By the end of the resolution, f_args contains the arguments that will be passed to the function for
490
494
  evaluation. It is possible for f_args to finally contain None if the function is itself being projected.
491
495
  - If `f.a` (the main function or symbol) resolves to another function `_f`, it might indicate a higher-order function scenario.
492
496
  - In such a case, `f_args` supplies a function as an argument to `f.a`.
@@ -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')
@@ -13,7 +13,7 @@ import numpy
13
13
 
14
14
  from .core import (KGChannel, KGChannelDir, KGLambda, KGSym, KlongException,
15
15
  is_dict, is_empty, is_list, kg_asarray, kg_read, kg_write, np,
16
- reserved_fn_args, reserved_fn_symbol_map, safe_eq)
16
+ reserved_fn_args, reserved_fn_symbol_map, safe_eq, safe_inspect)
17
17
 
18
18
 
19
19
  def eval_sys_append_channel(x):
@@ -341,7 +341,7 @@ def _handle_import(item):
341
341
  if n_args <= len(reserved_fn_args):
342
342
  item = KGLambda(item, args=reserved_fn_args[:n_args])
343
343
  else:
344
- args = inspect.signature(item, follow_wrapped=True).parameters
344
+ args = safe_inspect(item, follow_wrapped=True)
345
345
  if 'args' in args:
346
346
  item = KGLambda(item, args=None, wildcard=True)
347
347
  n_args = 3
@@ -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.7
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)
@@ -54,8 +64,6 @@ Requires-Dist: websockets==12.0; extra == "full"
54
64
  [![Downloads](https://static.pepy.tech/badge/klongpy/month)](https://pepy.tech/project/klongpy)
55
65
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
56
66
 
57
- [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/klongpy.svg?style=social&label=Follow%20%40KlongPy)](https://twitter.com/klongpy)
58
-
59
67
  # KlongPy: High-Performance Array Programming in Python
60
68
 
61
69
  KlongPy is a Python adaptation of the [Klong](https://t3x.org/klong) [array language](https://en.wikipedia.org/wiki/Array_programming), known for its high-performance vectorized operations that leverage the power of NumPy. Embracing a "batteries included" philosophy, KlongPy combines built-in modules with Python's expansive ecosystem, facilitating rapid application development with Klong's succinct syntax.
@@ -334,6 +342,31 @@ $ curl http://localhost:8888
334
342
  ['Hello, Klong World! ' 0 1 2 3 4 5 6 7 8 9]
335
343
  ```
336
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
+
337
370
  ## Conclusion
338
371
 
339
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.
@@ -390,6 +423,7 @@ KlongPy is effectively a superset of the Klong language, but has some key differ
390
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.
391
424
  * Python integration: Most notably, the ".py" command allows direct import of Python modules into the current Klong context.
392
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]]`.
393
427
 
394
428
  # Related
395
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.7',
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