flowquery 1.0.16 → 1.0.17

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 (318) hide show
  1. package/.github/workflows/python-publish.yml +97 -0
  2. package/dist/compute/runner.d.ts +3 -2
  3. package/dist/compute/runner.d.ts.map +1 -1
  4. package/dist/compute/runner.js +7 -7
  5. package/dist/compute/runner.js.map +1 -1
  6. package/dist/flowquery.min.js +1 -1
  7. package/dist/graph/data.d.ts +31 -0
  8. package/dist/graph/data.d.ts.map +1 -0
  9. package/dist/graph/data.js +110 -0
  10. package/dist/graph/data.js.map +1 -0
  11. package/dist/graph/database.d.ts +20 -0
  12. package/dist/graph/database.d.ts.map +1 -0
  13. package/dist/graph/database.js +77 -0
  14. package/dist/graph/database.js.map +1 -0
  15. package/dist/graph/hops.d.ts +11 -0
  16. package/dist/graph/hops.d.ts.map +1 -0
  17. package/dist/graph/hops.js +25 -0
  18. package/dist/graph/hops.js.map +1 -0
  19. package/dist/graph/node.d.ts +35 -0
  20. package/dist/graph/node.d.ts.map +1 -0
  21. package/dist/graph/node.js +113 -0
  22. package/dist/graph/node.js.map +1 -0
  23. package/dist/graph/node_data.d.ts +11 -0
  24. package/dist/graph/node_data.d.ts.map +1 -0
  25. package/dist/graph/node_data.js +20 -0
  26. package/dist/graph/node_data.js.map +1 -0
  27. package/dist/graph/node_reference.d.ts +10 -0
  28. package/dist/graph/node_reference.d.ts.map +1 -0
  29. package/dist/graph/node_reference.js +52 -0
  30. package/dist/graph/node_reference.js.map +1 -0
  31. package/dist/graph/pattern.d.ts +18 -0
  32. package/dist/graph/pattern.d.ts.map +1 -0
  33. package/dist/graph/pattern.js +114 -0
  34. package/dist/graph/pattern.js.map +1 -0
  35. package/dist/graph/pattern_expression.d.ts +14 -0
  36. package/dist/graph/pattern_expression.d.ts.map +1 -0
  37. package/dist/graph/pattern_expression.js +58 -0
  38. package/dist/graph/pattern_expression.js.map +1 -0
  39. package/dist/graph/patterns.d.ts +11 -0
  40. package/dist/graph/patterns.d.ts.map +1 -0
  41. package/dist/graph/patterns.js +49 -0
  42. package/dist/graph/patterns.js.map +1 -0
  43. package/dist/graph/physical_node.d.ts +10 -0
  44. package/dist/graph/physical_node.d.ts.map +1 -0
  45. package/dist/graph/physical_node.js +40 -0
  46. package/dist/graph/physical_node.js.map +1 -0
  47. package/dist/graph/physical_relationship.d.ts +10 -0
  48. package/dist/graph/physical_relationship.d.ts.map +1 -0
  49. package/dist/graph/physical_relationship.js +40 -0
  50. package/dist/graph/physical_relationship.js.map +1 -0
  51. package/dist/graph/relationship.d.ts +40 -0
  52. package/dist/graph/relationship.d.ts.map +1 -0
  53. package/dist/graph/relationship.js +124 -0
  54. package/dist/graph/relationship.js.map +1 -0
  55. package/dist/graph/relationship_data.d.ts +12 -0
  56. package/dist/graph/relationship_data.d.ts.map +1 -0
  57. package/dist/graph/relationship_data.js +40 -0
  58. package/dist/graph/relationship_data.js.map +1 -0
  59. package/dist/graph/relationship_match_collector.d.ts +19 -0
  60. package/dist/graph/relationship_match_collector.d.ts.map +1 -0
  61. package/dist/graph/relationship_match_collector.js +55 -0
  62. package/dist/graph/relationship_match_collector.js.map +1 -0
  63. package/dist/graph/relationship_reference.d.ts +8 -0
  64. package/dist/graph/relationship_reference.d.ts.map +1 -0
  65. package/dist/graph/relationship_reference.js +37 -0
  66. package/dist/graph/relationship_reference.js.map +1 -0
  67. package/dist/parsing/base_parser.d.ts +1 -0
  68. package/dist/parsing/base_parser.d.ts.map +1 -1
  69. package/dist/parsing/base_parser.js +4 -1
  70. package/dist/parsing/base_parser.js.map +1 -1
  71. package/dist/parsing/context.d.ts +2 -2
  72. package/dist/parsing/context.js +5 -5
  73. package/dist/parsing/expressions/boolean.d.ts +8 -0
  74. package/dist/parsing/expressions/boolean.d.ts.map +1 -0
  75. package/dist/parsing/expressions/boolean.js +26 -0
  76. package/dist/parsing/expressions/boolean.js.map +1 -0
  77. package/dist/parsing/expressions/expression.d.ts +4 -1
  78. package/dist/parsing/expressions/expression.d.ts.map +1 -1
  79. package/dist/parsing/expressions/expression.js +15 -8
  80. package/dist/parsing/expressions/expression.js.map +1 -1
  81. package/dist/parsing/expressions/operator.d.ts +1 -1
  82. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  83. package/dist/parsing/expressions/operator.js.map +1 -1
  84. package/dist/parsing/functions/function_factory.d.ts +13 -13
  85. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  86. package/dist/parsing/functions/function_factory.js +20 -18
  87. package/dist/parsing/functions/function_factory.js.map +1 -1
  88. package/dist/parsing/operations/create_node.d.ts +14 -0
  89. package/dist/parsing/operations/create_node.d.ts.map +1 -0
  90. package/dist/parsing/operations/create_node.js +51 -0
  91. package/dist/parsing/operations/create_node.js.map +1 -0
  92. package/dist/parsing/operations/create_relationship.d.ts +14 -0
  93. package/dist/parsing/operations/create_relationship.d.ts.map +1 -0
  94. package/dist/parsing/operations/create_relationship.js +51 -0
  95. package/dist/parsing/operations/create_relationship.js.map +1 -0
  96. package/dist/parsing/operations/match.d.ts +15 -0
  97. package/dist/parsing/operations/match.d.ts.map +1 -0
  98. package/dist/parsing/operations/match.js +45 -0
  99. package/dist/parsing/operations/match.js.map +1 -0
  100. package/dist/parsing/operations/operation.d.ts +1 -0
  101. package/dist/parsing/operations/operation.d.ts.map +1 -1
  102. package/dist/parsing/operations/operation.js +6 -0
  103. package/dist/parsing/operations/operation.js.map +1 -1
  104. package/dist/parsing/operations/return.d.ts +1 -0
  105. package/dist/parsing/operations/return.d.ts.map +1 -1
  106. package/dist/parsing/operations/return.js +7 -1
  107. package/dist/parsing/operations/return.js.map +1 -1
  108. package/dist/parsing/operations/where.d.ts +1 -1
  109. package/dist/parsing/operations/where.d.ts.map +1 -1
  110. package/dist/parsing/operations/where.js +4 -0
  111. package/dist/parsing/operations/where.js.map +1 -1
  112. package/dist/parsing/parser.d.ts +10 -0
  113. package/dist/parsing/parser.d.ts.map +1 -1
  114. package/dist/parsing/parser.js +344 -5
  115. package/dist/parsing/parser.js.map +1 -1
  116. package/dist/parsing/token_to_node.d.ts.map +1 -1
  117. package/dist/parsing/token_to_node.js +7 -0
  118. package/dist/parsing/token_to_node.js.map +1 -1
  119. package/dist/tokenization/keyword.d.ts +1 -0
  120. package/dist/tokenization/keyword.d.ts.map +1 -1
  121. package/dist/tokenization/keyword.js +1 -0
  122. package/dist/tokenization/keyword.js.map +1 -1
  123. package/dist/tokenization/token.d.ts +4 -0
  124. package/dist/tokenization/token.d.ts.map +1 -1
  125. package/dist/tokenization/token.js +14 -1
  126. package/dist/tokenization/token.js.map +1 -1
  127. package/dist/tokenization/token_type.d.ts +1 -0
  128. package/dist/tokenization/token_type.d.ts.map +1 -1
  129. package/dist/tokenization/token_type.js +1 -0
  130. package/dist/tokenization/token_type.js.map +1 -1
  131. package/dist/tokenization/tokenizer.d.ts +2 -1
  132. package/dist/tokenization/tokenizer.d.ts.map +1 -1
  133. package/dist/tokenization/tokenizer.js +25 -12
  134. package/dist/tokenization/tokenizer.js.map +1 -1
  135. package/docs/flowquery.min.js +1 -1
  136. package/flowquery-py/README.md +166 -0
  137. package/flowquery-py/pyproject.toml +75 -0
  138. package/flowquery-py/setup_env.ps1 +92 -0
  139. package/flowquery-py/setup_env.sh +87 -0
  140. package/flowquery-py/src/__init__.py +34 -0
  141. package/flowquery-py/src/__main__.py +10 -0
  142. package/flowquery-py/src/compute/__init__.py +5 -0
  143. package/flowquery-py/src/compute/runner.py +60 -0
  144. package/flowquery-py/src/extensibility.py +52 -0
  145. package/flowquery-py/src/graph/__init__.py +31 -0
  146. package/flowquery-py/src/graph/data.py +118 -0
  147. package/flowquery-py/src/graph/database.py +82 -0
  148. package/flowquery-py/src/graph/hops.py +43 -0
  149. package/flowquery-py/src/graph/node.py +112 -0
  150. package/flowquery-py/src/graph/node_data.py +26 -0
  151. package/flowquery-py/src/graph/node_reference.py +49 -0
  152. package/flowquery-py/src/graph/pattern.py +125 -0
  153. package/flowquery-py/src/graph/pattern_expression.py +62 -0
  154. package/flowquery-py/src/graph/patterns.py +42 -0
  155. package/flowquery-py/src/graph/physical_node.py +40 -0
  156. package/flowquery-py/src/graph/physical_relationship.py +36 -0
  157. package/flowquery-py/src/graph/relationship.py +135 -0
  158. package/flowquery-py/src/graph/relationship_data.py +33 -0
  159. package/flowquery-py/src/graph/relationship_match_collector.py +77 -0
  160. package/flowquery-py/src/graph/relationship_reference.py +21 -0
  161. package/flowquery-py/src/io/__init__.py +5 -0
  162. package/flowquery-py/src/io/command_line.py +67 -0
  163. package/flowquery-py/src/parsing/__init__.py +17 -0
  164. package/flowquery-py/src/parsing/alias.py +20 -0
  165. package/flowquery-py/src/parsing/alias_option.py +11 -0
  166. package/flowquery-py/src/parsing/ast_node.py +146 -0
  167. package/flowquery-py/src/parsing/base_parser.py +84 -0
  168. package/flowquery-py/src/parsing/components/__init__.py +19 -0
  169. package/flowquery-py/src/parsing/components/csv.py +8 -0
  170. package/flowquery-py/src/parsing/components/from_.py +10 -0
  171. package/flowquery-py/src/parsing/components/headers.py +12 -0
  172. package/flowquery-py/src/parsing/components/json.py +8 -0
  173. package/flowquery-py/src/parsing/components/null.py +10 -0
  174. package/flowquery-py/src/parsing/components/post.py +8 -0
  175. package/flowquery-py/src/parsing/components/text.py +8 -0
  176. package/flowquery-py/src/parsing/context.py +50 -0
  177. package/flowquery-py/src/parsing/data_structures/__init__.py +15 -0
  178. package/flowquery-py/src/parsing/data_structures/associative_array.py +41 -0
  179. package/flowquery-py/src/parsing/data_structures/json_array.py +30 -0
  180. package/flowquery-py/src/parsing/data_structures/key_value_pair.py +38 -0
  181. package/flowquery-py/src/parsing/data_structures/lookup.py +49 -0
  182. package/flowquery-py/src/parsing/data_structures/range_lookup.py +42 -0
  183. package/flowquery-py/src/parsing/expressions/__init__.py +57 -0
  184. package/flowquery-py/src/parsing/expressions/boolean.py +20 -0
  185. package/flowquery-py/src/parsing/expressions/expression.py +138 -0
  186. package/flowquery-py/src/parsing/expressions/expression_map.py +26 -0
  187. package/flowquery-py/src/parsing/expressions/f_string.py +27 -0
  188. package/flowquery-py/src/parsing/expressions/identifier.py +20 -0
  189. package/flowquery-py/src/parsing/expressions/number.py +32 -0
  190. package/flowquery-py/src/parsing/expressions/operator.py +169 -0
  191. package/flowquery-py/src/parsing/expressions/reference.py +47 -0
  192. package/flowquery-py/src/parsing/expressions/string.py +27 -0
  193. package/flowquery-py/src/parsing/functions/__init__.py +75 -0
  194. package/flowquery-py/src/parsing/functions/aggregate_function.py +60 -0
  195. package/flowquery-py/src/parsing/functions/async_function.py +62 -0
  196. package/flowquery-py/src/parsing/functions/avg.py +55 -0
  197. package/flowquery-py/src/parsing/functions/collect.py +75 -0
  198. package/flowquery-py/src/parsing/functions/function.py +68 -0
  199. package/flowquery-py/src/parsing/functions/function_factory.py +173 -0
  200. package/flowquery-py/src/parsing/functions/function_metadata.py +149 -0
  201. package/flowquery-py/src/parsing/functions/functions.py +59 -0
  202. package/flowquery-py/src/parsing/functions/join.py +47 -0
  203. package/flowquery-py/src/parsing/functions/keys.py +34 -0
  204. package/flowquery-py/src/parsing/functions/predicate_function.py +46 -0
  205. package/flowquery-py/src/parsing/functions/predicate_sum.py +47 -0
  206. package/flowquery-py/src/parsing/functions/rand.py +28 -0
  207. package/flowquery-py/src/parsing/functions/range_.py +34 -0
  208. package/flowquery-py/src/parsing/functions/reducer_element.py +15 -0
  209. package/flowquery-py/src/parsing/functions/replace.py +37 -0
  210. package/flowquery-py/src/parsing/functions/round_.py +32 -0
  211. package/flowquery-py/src/parsing/functions/size.py +32 -0
  212. package/flowquery-py/src/parsing/functions/split.py +47 -0
  213. package/flowquery-py/src/parsing/functions/stringify.py +47 -0
  214. package/flowquery-py/src/parsing/functions/sum.py +51 -0
  215. package/flowquery-py/src/parsing/functions/to_json.py +33 -0
  216. package/flowquery-py/src/parsing/functions/type_.py +47 -0
  217. package/flowquery-py/src/parsing/functions/value_holder.py +24 -0
  218. package/flowquery-py/src/parsing/logic/__init__.py +15 -0
  219. package/flowquery-py/src/parsing/logic/case.py +29 -0
  220. package/flowquery-py/src/parsing/logic/else_.py +12 -0
  221. package/flowquery-py/src/parsing/logic/end.py +8 -0
  222. package/flowquery-py/src/parsing/logic/then.py +12 -0
  223. package/flowquery-py/src/parsing/logic/when.py +10 -0
  224. package/flowquery-py/src/parsing/operations/__init__.py +35 -0
  225. package/flowquery-py/src/parsing/operations/aggregated_return.py +24 -0
  226. package/flowquery-py/src/parsing/operations/aggregated_with.py +22 -0
  227. package/flowquery-py/src/parsing/operations/call.py +74 -0
  228. package/flowquery-py/src/parsing/operations/create_node.py +34 -0
  229. package/flowquery-py/src/parsing/operations/create_relationship.py +34 -0
  230. package/flowquery-py/src/parsing/operations/group_by.py +130 -0
  231. package/flowquery-py/src/parsing/operations/limit.py +22 -0
  232. package/flowquery-py/src/parsing/operations/load.py +140 -0
  233. package/flowquery-py/src/parsing/operations/match.py +29 -0
  234. package/flowquery-py/src/parsing/operations/operation.py +69 -0
  235. package/flowquery-py/src/parsing/operations/projection.py +21 -0
  236. package/flowquery-py/src/parsing/operations/return_op.py +50 -0
  237. package/flowquery-py/src/parsing/operations/unwind.py +37 -0
  238. package/flowquery-py/src/parsing/operations/where.py +41 -0
  239. package/flowquery-py/src/parsing/operations/with_op.py +18 -0
  240. package/flowquery-py/src/parsing/parser.py +1011 -0
  241. package/flowquery-py/src/parsing/token_to_node.py +109 -0
  242. package/flowquery-py/src/tokenization/__init__.py +23 -0
  243. package/flowquery-py/src/tokenization/keyword.py +48 -0
  244. package/flowquery-py/src/tokenization/operator.py +29 -0
  245. package/flowquery-py/src/tokenization/string_walker.py +158 -0
  246. package/flowquery-py/src/tokenization/symbol.py +19 -0
  247. package/flowquery-py/src/tokenization/token.py +659 -0
  248. package/flowquery-py/src/tokenization/token_mapper.py +52 -0
  249. package/flowquery-py/src/tokenization/token_type.py +21 -0
  250. package/flowquery-py/src/tokenization/tokenizer.py +214 -0
  251. package/flowquery-py/src/tokenization/trie.py +124 -0
  252. package/flowquery-py/src/utils/__init__.py +6 -0
  253. package/flowquery-py/src/utils/object_utils.py +20 -0
  254. package/flowquery-py/src/utils/string_utils.py +113 -0
  255. package/flowquery-py/tests/__init__.py +1 -0
  256. package/flowquery-py/tests/compute/__init__.py +1 -0
  257. package/flowquery-py/tests/compute/test_runner.py +1335 -0
  258. package/flowquery-py/tests/graph/__init__.py +1 -0
  259. package/flowquery-py/tests/graph/test_create.py +56 -0
  260. package/flowquery-py/tests/graph/test_data.py +73 -0
  261. package/flowquery-py/tests/graph/test_match.py +40 -0
  262. package/flowquery-py/tests/parsing/__init__.py +1 -0
  263. package/flowquery-py/tests/parsing/test_context.py +34 -0
  264. package/flowquery-py/tests/parsing/test_expression.py +49 -0
  265. package/flowquery-py/tests/parsing/test_parser.py +674 -0
  266. package/flowquery-py/tests/test_extensibility.py +611 -0
  267. package/flowquery-py/tests/tokenization/__init__.py +1 -0
  268. package/flowquery-py/tests/tokenization/test_token_mapper.py +60 -0
  269. package/flowquery-py/tests/tokenization/test_tokenizer.py +164 -0
  270. package/flowquery-py/tests/tokenization/test_trie.py +30 -0
  271. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  272. package/misc/apps/RAG/package.json +1 -1
  273. package/misc/apps/RAG/src/components/AdaptiveCardRenderer.tsx +76 -8
  274. package/misc/apps/RAG/src/components/index.ts +19 -10
  275. package/misc/apps/RAG/src/plugins/loaders/MockData.ts +70 -140
  276. package/misc/apps/RAG/src/prompts/FlowQuerySystemPrompt.ts +12 -0
  277. package/package.json +1 -1
  278. package/src/compute/runner.ts +24 -19
  279. package/src/graph/data.ts +112 -0
  280. package/src/graph/database.ts +63 -0
  281. package/src/graph/hops.ts +22 -0
  282. package/src/graph/node.ts +99 -0
  283. package/src/graph/node_data.ts +18 -0
  284. package/src/graph/node_reference.ts +33 -0
  285. package/src/graph/pattern.ts +101 -0
  286. package/src/graph/pattern_expression.ts +37 -0
  287. package/src/graph/patterns.ts +36 -0
  288. package/src/graph/physical_node.ts +23 -0
  289. package/src/graph/physical_relationship.ts +23 -0
  290. package/src/graph/relationship.ts +116 -0
  291. package/src/graph/relationship_data.ts +27 -0
  292. package/src/graph/relationship_match_collector.ts +58 -0
  293. package/src/graph/relationship_reference.ts +24 -0
  294. package/src/parsing/base_parser.ts +20 -14
  295. package/src/parsing/context.ts +14 -14
  296. package/src/parsing/expressions/boolean.ts +21 -0
  297. package/src/parsing/expressions/expression.ts +34 -26
  298. package/src/parsing/expressions/operator.ts +19 -1
  299. package/src/parsing/functions/function_factory.ts +45 -45
  300. package/src/parsing/operations/create_node.ts +39 -0
  301. package/src/parsing/operations/create_relationship.ts +38 -0
  302. package/src/parsing/operations/match.ts +31 -0
  303. package/src/parsing/operations/operation.ts +3 -0
  304. package/src/parsing/operations/return.ts +11 -7
  305. package/src/parsing/operations/where.ts +10 -6
  306. package/src/parsing/parser.ts +346 -8
  307. package/src/parsing/token_to_node.ts +6 -0
  308. package/src/tokenization/keyword.ts +41 -40
  309. package/src/tokenization/token.ts +21 -1
  310. package/src/tokenization/token_type.ts +2 -1
  311. package/src/tokenization/tokenizer.ts +52 -31
  312. package/tests/compute/runner.test.ts +654 -0
  313. package/tests/extensibility.test.ts +97 -93
  314. package/tests/graph/create.test.ts +36 -0
  315. package/tests/graph/data.test.ts +58 -0
  316. package/tests/graph/match.test.ts +29 -0
  317. package/tests/parsing/parser.test.ts +273 -2
  318. package/tests/tokenization/tokenizer.test.ts +90 -0
@@ -0,0 +1,611 @@
1
+ """Tests for the FlowQuery extensibility API."""
2
+
3
+ import pytest
4
+ from typing import TypedDict, Optional, List, Any
5
+ from flowquery.parsing.functions.function import Function
6
+ from flowquery.parsing.functions.aggregate_function import AggregateFunction
7
+ from flowquery.parsing.functions.async_function import AsyncFunction
8
+ from flowquery.parsing.functions.predicate_function import PredicateFunction
9
+ from flowquery.parsing.functions.reducer_element import ReducerElement
10
+ from flowquery.parsing.functions.function_metadata import (
11
+ FunctionDef,
12
+ FunctionMetadata,
13
+ FunctionCategory,
14
+ ParameterSchema,
15
+ OutputSchema,
16
+ FunctionDefOptions,
17
+ get_function_metadata,
18
+ get_registered_function_factory,
19
+ )
20
+
21
+
22
+ class TestExtensibilityExports:
23
+ """Test cases for the extensibility API."""
24
+
25
+ def test_function_class_can_be_extended(self):
26
+ """Function class is exported and can be extended."""
27
+ class CustomFunction(Function):
28
+ def __init__(self):
29
+ super().__init__("customFunc")
30
+ self._expected_parameter_count = 1
31
+
32
+ def value(self) -> str:
33
+ return "custom value"
34
+
35
+ func = CustomFunction()
36
+ assert func.name == "customFunc"
37
+ assert str(func) == "Function (customFunc)"
38
+ assert func.value() == "custom value"
39
+
40
+ def test_function_validates_parameter_count(self):
41
+ """Function validates parameter count when set."""
42
+ class TwoParamFunction(Function):
43
+ def __init__(self):
44
+ super().__init__("twoParam")
45
+ self._expected_parameter_count = 2
46
+
47
+ func = TwoParamFunction()
48
+
49
+ # Should throw when wrong number of parameters
50
+ with pytest.raises(ValueError, match="Function twoParam expected 2 parameters, but got 0"):
51
+ func.parameters = []
52
+
53
+ def test_function_without_expected_count_accepts_any(self):
54
+ """Function without expected parameter count accepts any number."""
55
+ class FlexibleFunction(Function):
56
+ def __init__(self):
57
+ super().__init__("flexible")
58
+ # _expected_parameter_count is None by default
59
+
60
+ func = FlexibleFunction()
61
+ # Should not throw
62
+ func.parameters = []
63
+ assert len(func.get_children()) == 0
64
+
65
+
66
+ class TestAggregateFunctionExtension:
67
+ """Test cases for AggregateFunction extension."""
68
+
69
+ def test_aggregate_function_can_be_extended(self):
70
+ """AggregateFunction class is exported and can be extended."""
71
+ class SumElement(ReducerElement):
72
+ def __init__(self):
73
+ self._value: float = 0
74
+
75
+ @property
76
+ def value(self) -> float:
77
+ return self._value
78
+
79
+ @value.setter
80
+ def value(self, v: float) -> None:
81
+ self._value = v
82
+
83
+ class CustomSum(AggregateFunction):
84
+ def __init__(self):
85
+ super().__init__("customSum")
86
+ self._total: float = 0
87
+
88
+ def reduce(self, element: ReducerElement) -> None:
89
+ self._total += element.value
90
+
91
+ def element(self) -> ReducerElement:
92
+ el = SumElement()
93
+ el.value = self._total
94
+ return el
95
+
96
+ def value(self) -> float:
97
+ return self._total
98
+
99
+ func = CustomSum()
100
+ assert func.name == "customSum"
101
+
102
+
103
+ class TestFunctionDefDecorator:
104
+ """Test cases for the FunctionDef decorator."""
105
+
106
+ def test_function_def_decorator_registers_metadata(self):
107
+ """FunctionDef decorator registers function metadata."""
108
+ @FunctionDef({
109
+ "description": "Test function for unit testing",
110
+ "category": "scalar",
111
+ "parameters": [
112
+ {"name": "value", "description": "Input value", "type": "any"}
113
+ ],
114
+ "output": {"description": "Result", "type": "any"},
115
+ "examples": ["WITH test(1) AS x RETURN x"]
116
+ })
117
+ class TestFunction(Function):
118
+ def __init__(self):
119
+ super().__init__("testFunc")
120
+ self._expected_parameter_count = 1
121
+
122
+ def value(self):
123
+ return self.get_children()[0].value()
124
+
125
+ # Get the registered metadata using the function name (as registered by @FunctionDef)
126
+ metadata = get_function_metadata("testFunc", "scalar")
127
+ assert metadata is not None
128
+ assert metadata.description == "Test function for unit testing"
129
+ assert metadata.category == "scalar"
130
+ assert len(metadata.parameters) == 1
131
+ assert metadata.parameters[0]["name"] == "value"
132
+
133
+ def test_function_def_decorator_for_aggregate_function(self):
134
+ """FunctionDef decorator can be applied to an aggregate function."""
135
+ @FunctionDef({
136
+ "description": "Test aggregate function",
137
+ "category": "aggregate",
138
+ "parameters": [{"name": "value", "description": "Numeric value", "type": "number"}],
139
+ "output": {"description": "Aggregated result", "type": "number"},
140
+ })
141
+ class TestAggExt(AggregateFunction):
142
+ def __init__(self):
143
+ super().__init__("testAggExt")
144
+ self._sum = 0
145
+
146
+ def value(self):
147
+ return self._sum
148
+
149
+ instance = TestAggExt()
150
+ assert instance.name == "testAggExt"
151
+ assert instance.value() == 0
152
+
153
+ def test_function_def_decorator_for_predicate_function(self):
154
+ """FunctionDef decorator can be applied to a predicate function."""
155
+ @FunctionDef({
156
+ "description": "Test predicate function",
157
+ "category": "predicate",
158
+ "parameters": [{"name": "list", "description": "List to check", "type": "array"}],
159
+ "output": {"description": "Boolean result", "type": "boolean"},
160
+ })
161
+ class TestPredExt(PredicateFunction):
162
+ def __init__(self):
163
+ super().__init__("testPredExt")
164
+
165
+ def value(self):
166
+ return True
167
+
168
+ instance = TestPredExt()
169
+ assert instance.name == "testPredExt"
170
+ assert instance.value() is True
171
+
172
+ @pytest.mark.asyncio
173
+ async def test_function_def_decorator_for_async_provider(self):
174
+ """FunctionDef decorator can be applied to an async provider."""
175
+ from flowquery.parsing.functions.function_metadata import (
176
+ get_function_metadata,
177
+ get_registered_function_factory,
178
+ )
179
+
180
+ @FunctionDef({
181
+ "description": "Test async provider for extensibility",
182
+ "category": "async",
183
+ "parameters": [
184
+ {
185
+ "name": "count",
186
+ "description": "Number of items",
187
+ "type": "number",
188
+ "required": False,
189
+ "default": 1,
190
+ },
191
+ ],
192
+ "output": {"description": "Data object", "type": "object"},
193
+ })
194
+ class Simple(AsyncFunction):
195
+ async def generate(self, count: int = 1):
196
+ for i in range(count):
197
+ yield {"id": i, "data": f"item{i}"}
198
+
199
+ # Verify the decorated class still works correctly
200
+ loader = Simple("simple")
201
+ results = []
202
+ async for item in loader.generate(2):
203
+ results.append(item)
204
+ assert len(results) == 2
205
+ assert results[0] == {"id": 0, "data": "item0"}
206
+ assert results[1] == {"id": 1, "data": "item1"}
207
+
208
+ # Verify the async provider was registered (using class name)
209
+ provider = get_registered_function_factory("simple", "async")
210
+ assert provider is not None
211
+ assert callable(provider)
212
+
213
+ # Verify the metadata was registered
214
+ metadata = get_function_metadata("simple", "async")
215
+ assert metadata is not None
216
+ assert metadata.name == "simple"
217
+ assert metadata.category == "async"
218
+ assert metadata.description == "Test async provider for extensibility"
219
+
220
+
221
+ class TestPredicateFunctionExtension:
222
+ """Test cases for PredicateFunction extension."""
223
+
224
+ def test_predicate_function_can_be_extended(self):
225
+ """PredicateFunction class is exported and can be extended."""
226
+ class CustomPredicate(PredicateFunction):
227
+ def __init__(self):
228
+ super().__init__("customPredicate")
229
+
230
+ def value(self):
231
+ return True
232
+
233
+ pred = CustomPredicate()
234
+ assert pred.name == "customPredicate"
235
+ assert str(pred) == "PredicateFunction (customPredicate)"
236
+ assert pred.value() is True
237
+
238
+
239
+ class TestAsyncFunctionExtension:
240
+ """Test cases for AsyncFunction extension."""
241
+
242
+ def test_async_function_can_be_instantiated(self):
243
+ """AsyncFunction class is exported and can be instantiated."""
244
+ async_func = AsyncFunction("testAsync")
245
+ assert async_func.name == "testAsync"
246
+
247
+
248
+ class TestReducerElementExtension:
249
+ """Test cases for ReducerElement extension."""
250
+
251
+ def test_reducer_element_can_be_extended(self):
252
+ """ReducerElement class is exported and can be extended."""
253
+ class NumberElement(ReducerElement):
254
+ def __init__(self):
255
+ self._num = 0
256
+
257
+ @property
258
+ def value(self):
259
+ return self._num
260
+
261
+ @value.setter
262
+ def value(self, v):
263
+ self._num = v
264
+
265
+ elem = NumberElement()
266
+ elem.value = 42
267
+ assert elem.value == 42
268
+
269
+
270
+ class TestTypeExports:
271
+ """Test cases for type exports."""
272
+
273
+ def test_function_metadata_type(self):
274
+ """FunctionMetadata type can be used."""
275
+ meta = FunctionMetadata(
276
+ name="typeTest",
277
+ description="Testing type exports",
278
+ category="scalar",
279
+ parameters=[],
280
+ output={"description": "Output", "type": "string"},
281
+ )
282
+ assert meta.name == "typeTest"
283
+ assert meta.description == "Testing type exports"
284
+
285
+ def test_function_category_accepts_standard_and_custom(self):
286
+ """FunctionCategory type accepts standard and custom categories."""
287
+ scalar: FunctionCategory = "scalar"
288
+ aggregate: FunctionCategory = "aggregate"
289
+ predicate: FunctionCategory = "predicate"
290
+ async_cat: FunctionCategory = "async"
291
+ custom: FunctionCategory = "myCustomCategory"
292
+
293
+ assert scalar == "scalar"
294
+ assert aggregate == "aggregate"
295
+ assert predicate == "predicate"
296
+ assert async_cat == "async"
297
+ assert custom == "myCustomCategory"
298
+
299
+
300
+ class TestPluginFunctionsIntegration:
301
+ """Test cases for plugin functions integration with FlowQuery."""
302
+
303
+ @pytest.mark.asyncio
304
+ async def test_custom_scalar_function_in_query(self):
305
+ """Custom scalar function can be used in a FlowQuery statement."""
306
+ from flowquery.compute.runner import Runner
307
+
308
+ @FunctionDef({
309
+ "description": "Doubles a number",
310
+ "category": "scalar",
311
+ "parameters": [{"name": "value", "description": "Number to double", "type": "number"}],
312
+ "output": {"description": "Doubled value", "type": "number"},
313
+ })
314
+ class Double(Function):
315
+ def __init__(self):
316
+ super().__init__("double")
317
+ self._expected_parameter_count = 1
318
+
319
+ def value(self):
320
+ return self.get_children()[0].value() * 2
321
+
322
+ runner = Runner("WITH 5 AS num RETURN double(num) AS result")
323
+ await runner.run()
324
+
325
+ assert len(runner.results) == 1
326
+ assert runner.results[0] == {"result": 10}
327
+
328
+ @pytest.mark.asyncio
329
+ async def test_custom_string_function_in_query(self):
330
+ """Custom string function can be used in a FlowQuery statement."""
331
+ from flowquery.compute.runner import Runner
332
+
333
+ @FunctionDef({
334
+ "description": "Reverses a string",
335
+ "category": "scalar",
336
+ "parameters": [{"name": "text", "description": "String to reverse", "type": "string"}],
337
+ "output": {"description": "Reversed string", "type": "string"},
338
+ })
339
+ class StrReverse(Function):
340
+ def __init__(self):
341
+ super().__init__("strreverse")
342
+ self._expected_parameter_count = 1
343
+
344
+ def value(self):
345
+ input_str = str(self.get_children()[0].value())
346
+ return input_str[::-1]
347
+
348
+ runner = Runner("WITH 'hello' AS s RETURN strreverse(s) AS reversed")
349
+ await runner.run()
350
+
351
+ assert len(runner.results) == 1
352
+ assert runner.results[0] == {"reversed": "olleh"}
353
+
354
+ @pytest.mark.asyncio
355
+ async def test_custom_function_with_expressions(self):
356
+ """Custom function works with expressions and other functions."""
357
+ from flowquery.compute.runner import Runner
358
+
359
+ @FunctionDef({
360
+ "description": "Adds 100 to a number",
361
+ "category": "scalar",
362
+ "parameters": [{"name": "value", "description": "Number", "type": "number"}],
363
+ "output": {"description": "Number plus 100", "type": "number"},
364
+ })
365
+ class AddHundred(Function):
366
+ def __init__(self):
367
+ super().__init__("addhundred")
368
+ self._expected_parameter_count = 1
369
+
370
+ def value(self):
371
+ return self.get_children()[0].value() + 100
372
+
373
+ runner = Runner("WITH 5 * 3 AS num RETURN addhundred(num) + 1 AS result")
374
+ await runner.run()
375
+
376
+ assert len(runner.results) == 1
377
+ assert runner.results[0] == {"result": 116} # (5*3) + 100 + 1 = 116
378
+
379
+ @pytest.mark.asyncio
380
+ async def test_multiple_custom_functions_together(self):
381
+ """Multiple custom functions can be used together."""
382
+ from flowquery.compute.runner import Runner
383
+
384
+ @FunctionDef({
385
+ "description": "Triples a number",
386
+ "category": "scalar",
387
+ "parameters": [{"name": "value", "description": "Number to triple", "type": "number"}],
388
+ "output": {"description": "Tripled value", "type": "number"},
389
+ })
390
+ class Triple(Function):
391
+ def __init__(self):
392
+ super().__init__("triple")
393
+ self._expected_parameter_count = 1
394
+
395
+ def value(self):
396
+ return self.get_children()[0].value() * 3
397
+
398
+ @FunctionDef({
399
+ "description": "Squares a number",
400
+ "category": "scalar",
401
+ "parameters": [{"name": "value", "description": "Number to square", "type": "number"}],
402
+ "output": {"description": "Squared value", "type": "number"},
403
+ })
404
+ class Square(Function):
405
+ def __init__(self):
406
+ super().__init__("square")
407
+ self._expected_parameter_count = 1
408
+
409
+ def value(self):
410
+ v = self.get_children()[0].value()
411
+ return v * v
412
+
413
+ runner = Runner("WITH 2 AS num RETURN triple(num) AS tripled, square(num) AS squared")
414
+ await runner.run()
415
+
416
+ assert len(runner.results) == 1
417
+ assert runner.results[0] == {"tripled": 6, "squared": 4}
418
+
419
+ @pytest.mark.asyncio
420
+ async def test_custom_aggregate_function_in_query(self):
421
+ """Custom aggregate function can be used in a FlowQuery statement."""
422
+ from flowquery.compute.runner import Runner
423
+
424
+ # Custom reducer element for MinValue
425
+ class MinReducerElement(ReducerElement):
426
+ def __init__(self):
427
+ self._value = None
428
+
429
+ @property
430
+ def value(self):
431
+ return self._value
432
+
433
+ @value.setter
434
+ def value(self, val):
435
+ self._value = val
436
+
437
+ @FunctionDef({
438
+ "description": "Collects the minimum value",
439
+ "category": "aggregate",
440
+ "parameters": [{"name": "value", "description": "Value to compare", "type": "number"}],
441
+ "output": {"description": "Minimum value", "type": "number"},
442
+ })
443
+ class MinValue(AggregateFunction):
444
+ def __init__(self):
445
+ super().__init__("minvalue")
446
+ self._expected_parameter_count = 1
447
+
448
+ def reduce(self, element):
449
+ current = self.first_child().value()
450
+ if element.value is None or current < element.value:
451
+ element.value = current
452
+
453
+ def element(self):
454
+ return MinReducerElement()
455
+
456
+ runner = Runner("unwind [5, 2, 8, 1, 9] AS num RETURN minvalue(num) AS min")
457
+ await runner.run()
458
+
459
+ assert len(runner.results) == 1
460
+ assert runner.results[0] == {"min": 1}
461
+
462
+ @pytest.mark.asyncio
463
+ async def test_custom_async_provider_in_load_json_from_statement(self):
464
+ """Custom async provider can be used in LOAD JSON FROM statement."""
465
+ from flowquery.compute.runner import Runner
466
+
467
+ @FunctionDef({
468
+ "description": "Provides example data for testing",
469
+ "category": "async",
470
+ "parameters": [],
471
+ "output": {"description": "Example data object", "type": "object"},
472
+ })
473
+ class _GetExampleData(AsyncFunction):
474
+ def __init__(self):
475
+ super().__init__("getexampledata")
476
+ self._expected_parameter_count = 0
477
+
478
+ async def generate(self):
479
+ yield {"id": 1, "name": "Alice"}
480
+ yield {"id": 2, "name": "Bob"}
481
+
482
+ runner = Runner("LOAD JSON FROM getexampledata() AS data RETURN data.id AS id, data.name AS name")
483
+ await runner.run()
484
+
485
+ assert len(runner.results) == 2
486
+ assert runner.results[0] == {"id": 1, "name": "Alice"}
487
+ assert runner.results[1] == {"id": 2, "name": "Bob"}
488
+
489
+ @pytest.mark.asyncio
490
+ async def test_function_names_are_case_insensitive(self):
491
+ """Function names are case-insensitive."""
492
+ from flowquery.compute.runner import Runner
493
+
494
+ @FunctionDef({
495
+ "description": "Test function for case insensitivity",
496
+ "category": "async",
497
+ "parameters": [],
498
+ "output": {"description": "Test data", "type": "object"},
499
+ })
500
+ class _MixedCaseFunc(AsyncFunction):
501
+ def __init__(self):
502
+ super().__init__("mixedcasefunc")
503
+ self._expected_parameter_count = 0
504
+
505
+ async def generate(self):
506
+ yield {"value": 42}
507
+
508
+ # Test using different casings in FlowQuery statements
509
+ runner1 = Runner("LOAD JSON FROM mixedcasefunc() AS d RETURN d.value AS v")
510
+ await runner1.run()
511
+ assert runner1.results[0] == {"v": 42}
512
+
513
+ runner2 = Runner("LOAD JSON FROM MIXEDCASEFUNC() AS d RETURN d.value AS v")
514
+ await runner2.run()
515
+ assert runner2.results[0] == {"v": 42}
516
+
517
+ def test_parameter_schema_type_can_be_used(self):
518
+ """ParameterSchema type can be used."""
519
+ param: ParameterSchema = {
520
+ "name": "testParam",
521
+ "description": "A test parameter",
522
+ "type": "string",
523
+ "required": True,
524
+ "default": "default value",
525
+ "example": "example value",
526
+ }
527
+
528
+ assert param["name"] == "testParam"
529
+ assert param["required"] is True
530
+
531
+ def test_parameter_schema_with_nested_types(self):
532
+ """ParameterSchema with nested types."""
533
+ array_param: ParameterSchema = {
534
+ "name": "items",
535
+ "description": "Array of items",
536
+ "type": "array",
537
+ }
538
+
539
+ object_param: ParameterSchema = {
540
+ "name": "config",
541
+ "description": "Configuration object",
542
+ "type": "object",
543
+ }
544
+
545
+ assert array_param["type"] == "array"
546
+ assert object_param["type"] == "object"
547
+
548
+ def test_output_schema_type_can_be_used(self):
549
+ """OutputSchema type can be used."""
550
+ output: OutputSchema = {
551
+ "description": "Result output",
552
+ "type": "object",
553
+ "example": {"success": True, "data": []},
554
+ }
555
+
556
+ assert output["type"] == "object"
557
+ assert output["example"]["success"] is True
558
+
559
+ def test_function_def_options_type_can_be_used(self):
560
+ """FunctionDefOptions type can be used."""
561
+ options: FunctionDefOptions = {
562
+ "description": "Function options test",
563
+ "category": "scalar",
564
+ "parameters": [],
565
+ "output": {"description": "Output", "type": "string"},
566
+ "notes": "Some additional notes",
567
+ }
568
+
569
+ assert options["description"] == "Function options test"
570
+ assert options["notes"] == "Some additional notes"
571
+
572
+ @pytest.mark.asyncio
573
+ async def test_custom_function_retrieved_via_functions(self):
574
+ """Custom function can be retrieved via functions() in a FlowQuery statement."""
575
+ from flowquery.extensibility import FunctionDef
576
+ from flowquery.parsing.functions.function import Function
577
+ from flowquery.parsing.functions.function_metadata import get_function_metadata
578
+ from flowquery.compute.runner import Runner
579
+
580
+ @FunctionDef({
581
+ "description": "A unique test function for introspection",
582
+ "category": "scalar",
583
+ "parameters": [{"name": "x", "description": "Input value", "type": "number"}],
584
+ "output": {"description": "Output value", "type": "number"},
585
+ })
586
+ class IntrospectTestFunc(Function):
587
+ def __init__(self):
588
+ super().__init__("introspectTestFunc")
589
+ self._expected_parameter_count = 1
590
+
591
+ def value(self):
592
+ return self.get_children()[0].value() + 42
593
+
594
+ # First verify the function is registered
595
+ metadata = get_function_metadata("introspectTestFunc")
596
+ assert metadata is not None
597
+ assert metadata.name == "introspecttestfunc"
598
+
599
+ # Use functions() with UNWIND to find the registered function
600
+ runner = Runner("""
601
+ WITH functions() AS funcs
602
+ UNWIND funcs AS f
603
+ WITH f WHERE f.name = 'introspecttestfunc'
604
+ RETURN f.name AS name, f.description AS description, f.category AS category
605
+ """)
606
+ await runner.run()
607
+
608
+ assert len(runner.results) == 1
609
+ assert runner.results[0]["name"] == "introspecttestfunc"
610
+ assert runner.results[0]["description"] == "A unique test function for introspection"
611
+ assert runner.results[0]["category"] == "scalar"
@@ -0,0 +1 @@
1
+ """Tokenization tests package."""
@@ -0,0 +1,60 @@
1
+ """Tests for the TokenMapper class."""
2
+
3
+ import pytest
4
+ from flowquery.tokenization.token_mapper import TokenMapper
5
+ from flowquery.tokenization.symbol import Symbol
6
+ from flowquery.tokenization.keyword import Keyword
7
+ from flowquery.tokenization.operator import Operator
8
+
9
+
10
+ class TestTokenMapper:
11
+ """Test cases for the TokenMapper class."""
12
+
13
+ def test_mapper_with_symbols(self):
14
+ """Test mapper with Symbol enum."""
15
+ mapper = TokenMapper(Symbol)
16
+
17
+ assert mapper.map(Symbol.LEFT_PARENTHESIS.value) is not None
18
+ assert mapper.map(Symbol.RIGHT_PARENTHESIS.value) is not None
19
+ assert mapper.map(Symbol.COMMA.value) is not None
20
+ assert mapper.map(Symbol.DOT.value) is not None
21
+ assert mapper.map(Symbol.COLON.value) is not None
22
+
23
+ # Operator should not be found in symbol mapper
24
+ assert mapper.map(Operator.ADD.value) is None
25
+
26
+ def test_mapper_with_keywords(self):
27
+ """Test mapper with Keyword enum."""
28
+ mapper = TokenMapper(Keyword)
29
+
30
+ assert mapper.map(Keyword.MATCH.value) is not None
31
+ assert mapper.map(Keyword.RETURN.value) is not None
32
+ assert mapper.map(Keyword.WHERE.value) is not None
33
+
34
+ assert mapper.map("not_a_keyword") is None
35
+
36
+ def test_mapper_with_operators(self):
37
+ """Test mapper with Operator enum."""
38
+ mapper = TokenMapper(Operator)
39
+
40
+ assert mapper.map(Operator.GREATER_THAN_OR_EQUAL.value) is not None
41
+ assert mapper.map(Operator.ADD.value) is not None
42
+ assert mapper.map(Operator.SUBTRACT.value) is not None
43
+ assert mapper.map(Operator.NOT.value) is not None
44
+ assert mapper.map(Operator.EQUALS.value) is not None
45
+ assert mapper.map(Operator.NOT_EQUALS.value) is not None
46
+ assert mapper.map(Operator.LESS_THAN.value) is not None
47
+ assert mapper.map(Operator.LESS_THAN_OR_EQUAL.value) is not None
48
+
49
+ # Partial match should still work
50
+ assert mapper.map(Operator.GREATER_THAN_OR_EQUAL.value + "1") is not None
51
+
52
+ assert mapper.map("i_s_n_o_t_an_operator") is None
53
+
54
+ def test_mapper_with_mixed_types(self):
55
+ """Test mapper with mixed types."""
56
+ mapper = TokenMapper(Symbol)
57
+
58
+ assert mapper.map(Symbol.LEFT_PARENTHESIS.value) is not None
59
+ assert mapper.map(Symbol.RIGHT_PARENTHESIS.value) is not None
60
+ assert mapper.map(Symbol.COMMA.value) is not None