tellaro-query-language 0.2.7__tar.gz → 0.2.8__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 (58) hide show
  1. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/PKG-INFO +10 -9
  2. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/pyproject.toml +9 -8
  3. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator_components/value_comparison.py +10 -8
  4. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser.py +78 -20
  5. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/grammar.py +19 -1
  6. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/LICENSE +0 -0
  7. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/README.md +0 -0
  8. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/__init__.py +0 -0
  9. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/analyzer.py +0 -0
  10. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/cache/__init__.py +0 -0
  11. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/cache/base.py +0 -0
  12. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/cache/memory.py +0 -0
  13. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/cache/redis.py +0 -0
  14. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/cli.py +0 -0
  15. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core.py +0 -0
  16. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/README.md +0 -0
  17. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/__init__.py +0 -0
  18. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/file_operations.py +0 -0
  19. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/opensearch_operations.py +0 -0
  20. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/stats_operations.py +0 -0
  21. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/core_components/validation_operations.py +0 -0
  22. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator.py +0 -0
  23. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator_components/README.md +0 -0
  24. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator_components/__init__.py +0 -0
  25. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator_components/field_access.py +0 -0
  26. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/evaluator_components/special_expressions.py +0 -0
  27. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/exceptions.py +0 -0
  28. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/field_type_inference.py +0 -0
  29. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/geoip_normalizer.py +0 -0
  30. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutator_analyzer.py +0 -0
  31. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/__init__.py +0 -0
  32. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/base.py +0 -0
  33. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/dns.py +0 -0
  34. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/encoding.py +0 -0
  35. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/geo.py +0 -0
  36. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/list.py +0 -0
  37. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/network.py +0 -0
  38. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/security.py +0 -0
  39. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/mutators/string.py +0 -0
  40. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch.py +0 -0
  41. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_components/README.md +0 -0
  42. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_components/__init__.py +0 -0
  43. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_components/field_mapping.py +0 -0
  44. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_components/lucene_converter.py +0 -0
  45. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_components/query_converter.py +0 -0
  46. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_mappings.py +0 -0
  47. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/opensearch_stats.py +0 -0
  48. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/README.md +0 -0
  49. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/__init__.py +0 -0
  50. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/ast_builder.py +0 -0
  51. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/error_analyzer.py +0 -0
  52. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/parser_components/field_extractor.py +0 -0
  53. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/post_processor.py +0 -0
  54. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/scripts.py +0 -0
  55. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/stats_evaluator.py +0 -0
  56. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/stats_transformer.py +0 -0
  57. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/streaming_file_processor.py +0 -0
  58. {tellaro_query_language-0.2.7 → tellaro_query_language-0.2.8}/src/tql/validators.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tellaro-query-language
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: A flexible, human-friendly query language for searching and filtering structured data
5
- Home-page: https://github.com/tellaro/tellaro-query-language
6
5
  License: Proprietary
6
+ License-File: LICENSE
7
7
  Keywords: query,language,opensearch,elasticsearch,search,filter,tql
8
8
  Author: Justin Henderson
9
9
  Author-email: justin@tellaro.io
@@ -18,14 +18,15 @@ Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Classifier: Topic :: Text Processing :: Linguistic
20
20
  Provides-Extra: opensearch
21
- Requires-Dist: dnspython (>=2.7.0,<3.0.0)
22
- Requires-Dist: maxminddb (>=2.7.0,<3.0.0)
21
+ Requires-Dist: dnspython (>=2.8.0,<3.0.0)
22
+ Requires-Dist: maxminddb (>=3.0.0,<4.0.0)
23
23
  Requires-Dist: opensearch-dsl (>=2.1.0,<3.0.0) ; extra == "opensearch"
24
- Requires-Dist: opensearch-py (>=2.4.2,<3.0.0) ; extra == "opensearch"
25
- Requires-Dist: pyparsing (>=3.2.1,<4.0.0)
26
- Requires-Dist: setuptools (>=80.0.0,<81.0.0)
27
- Requires-Dist: urllib3 (>=2.5.0,<3.0.0)
24
+ Requires-Dist: opensearch-py (>=2.8.0,<3.0.0) ; extra == "opensearch"
25
+ Requires-Dist: pyparsing (>=3.3.0,<4.0.0)
26
+ Requires-Dist: setuptools (>=80.9.0,<81.0.0)
27
+ Requires-Dist: urllib3 (>=2.6.0,<3.0.0)
28
28
  Project-URL: Documentation, https://github.com/tellaro/tellaro-query-language/tree/main/docs
29
+ Project-URL: Homepage, https://github.com/tellaro/tellaro-query-language
29
30
  Project-URL: Repository, https://github.com/tellaro/tellaro-query-language
30
31
  Description-Content-Type: text/markdown
31
32
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "tellaro-query-language"
3
- version = "0.2.7"
3
+ version = "0.2.8"
4
4
  description = "A flexible, human-friendly query language for searching and filtering structured data"
5
5
  authors = ["Justin Henderson <justin@tellaro.io>"]
6
6
  license = "Proprietary"
@@ -34,13 +34,13 @@ tests = "tql.scripts:run_tests"
34
34
 
35
35
  [tool.poetry.dependencies]
36
36
  python = ">=3.11,<3.14"
37
- pyparsing = "^3.2.1"
38
- setuptools = "^80.0.0"
39
- dnspython = "^2.7.0"
40
- opensearch-py = {version = "^2.4.2", optional = true}
37
+ pyparsing = "^3.3.0"
38
+ setuptools = "^80.9.0"
39
+ dnspython = "^2.8.0"
40
+ opensearch-py = {version = "^2.8.0", optional = true}
41
41
  opensearch-dsl = {version = "^2.1.0", optional = true}
42
- maxminddb = "^2.7.0"
43
- urllib3 = "^2.5.0"
42
+ maxminddb = "^3.0.0"
43
+ urllib3 = "^2.6.0"
44
44
 
45
45
  [tool.poetry.extras]
46
46
  opensearch = ["opensearch-py", "opensearch-dsl"]
@@ -76,7 +76,8 @@ vulture = "^2.14"
76
76
  types-setuptools = "^75.8.2.20250305"
77
77
  opensearch-dsl = "^2.1.0"
78
78
  python-dotenv = "^1.1.0"
79
- jupyter = "^1.1.1"
79
+ # jupyter removed due to CVE-2025-53000 (nbconvert vulnerability, no patch available)
80
+ # Use ipykernel for IDE notebook support; reinstall jupyter manually if needed
80
81
  redis = "^7.1.0"
81
82
 
82
83
  [build-system]
@@ -170,15 +170,17 @@ class ValueComparator:
170
170
  return str(field_value).lower().endswith(str(expected_value).lower())
171
171
  elif operator == "in":
172
172
  if isinstance(expected_value, list):
173
- if len(expected_value) == 1 and isinstance(field_value, list):
174
- # This is likely a reversed 'in' case: 'value' in field_list
175
- # Check if the single expected value is in the field list
176
- converted_expected = self._convert_numeric(expected_value[0])
177
- return converted_expected in field_value
173
+ # Convert list elements to appropriate types for comparison
174
+ converted_list = [self._convert_numeric(val) for val in expected_value]
175
+
176
+ if isinstance(field_value, (list, tuple)):
177
+ # Field is an array - check if ANY element of field equals ANY value in list
178
+ # This handles both:
179
+ # - "value" in field (single expected value, array field)
180
+ # - field in ["val1", "val2"] (multiple expected values, array field)
181
+ return any(elem in converted_list for elem in field_value)
178
182
  else:
179
- # Standard case: field_value in list
180
- # Convert list elements to appropriate types for comparison
181
- converted_list = [self._convert_numeric(val) for val in expected_value]
183
+ # Scalar field - check if field_value is in the list
182
184
  return field_value in converted_list
183
185
  else:
184
186
  return field_value == expected_value
@@ -332,6 +332,29 @@ class TQLParser:
332
332
  return result
333
333
 
334
334
  if len(parsed) == 4:
335
+ # Check for field_in_values marker: field in [values] __field_in_values__
336
+ if isinstance(parsed[3], str) and parsed[3] == "__field_in_values__":
337
+ # This is field in [values] syntax
338
+ field_part, op, values_list, marker = parsed
339
+ field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(field_part)
340
+ # Extract values from the list
341
+ values = []
342
+ for item in values_list:
343
+ if isinstance(item, list) and len(item) >= 1:
344
+ values.append(item[0] if len(item) == 1 else item)
345
+ else:
346
+ values.append(item)
347
+ result = {
348
+ "type": "comparison",
349
+ "field": field_name,
350
+ "type_hint": type_hint,
351
+ "operator": "in",
352
+ "value": values,
353
+ }
354
+ if field_mutators:
355
+ result["field_mutators"] = field_mutators
356
+ return result
357
+
335
358
  # Check for ANY/ALL operators: ANY field op value
336
359
  first, field, operator, value = parsed
337
360
 
@@ -368,12 +391,25 @@ class TQLParser:
368
391
  normalized_operator = "any"
369
392
  else:
370
393
  normalized_operator = f"not_{third.lower()}"
394
+
395
+ # Extract value properly - unwrap if it's a double-wrapped list
396
+ # This happens for "field not in [values]" where the list is wrapped twice
397
+ value = fourth
398
+ if (
399
+ third.lower() == "in"
400
+ and isinstance(fourth, list)
401
+ and len(fourth) == 1
402
+ and isinstance(fourth[0], list)
403
+ ):
404
+ # Unwrap the double-wrapped list for "not in" with values
405
+ value = fourth[0]
406
+
371
407
  result = {
372
408
  "type": "comparison",
373
409
  "field": field_name,
374
410
  "type_hint": type_hint,
375
411
  "operator": normalized_operator,
376
- "value": fourth,
412
+ "value": value,
377
413
  }
378
414
  if field_mutators:
379
415
  result["field_mutators"] = field_mutators
@@ -577,6 +613,29 @@ class TQLParser:
577
613
  result["value_mutators"] = value_mutators
578
614
  return result
579
615
  elif len(parsed) == 5:
616
+ # Check for field_not_in_values marker: field not in [values] __field_not_in_values__
617
+ if isinstance(parsed[4], str) and parsed[4] == "__field_not_in_values__":
618
+ # This is field not in [values] syntax
619
+ field_part, not_kw, in_kw, values_list, marker = parsed
620
+ field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(field_part)
621
+ # Extract values from the list
622
+ values = []
623
+ for item in values_list:
624
+ if isinstance(item, list) and len(item) >= 1:
625
+ values.append(item[0] if len(item) == 1 else item)
626
+ else:
627
+ values.append(item)
628
+ result = {
629
+ "type": "comparison",
630
+ "field": field_name,
631
+ "type_hint": type_hint,
632
+ "operator": "not_in",
633
+ "value": values,
634
+ }
635
+ if field_mutators:
636
+ result["field_mutators"] = field_mutators
637
+ return result
638
+
580
639
  # Check for natural between syntax: field between value1 and value2
581
640
  # Only process as between if the second element is "between"
582
641
  if (
@@ -996,7 +1055,7 @@ class TQLParser:
996
1055
  return {"type": "unknown", "value": parsed}
997
1056
  else:
998
1057
  # Comparison operation
999
- # Handle 'in' operator - always value in field(s)
1058
+ # Handle 'in' operator - can be "value in field(s)" or "field in [values]"
1000
1059
  if isinstance(operator, str) and operator.lower() == "in":
1001
1060
  # Check for old syntax: [field1, field2] in value
1002
1061
  # The parser wraps list literals, so check for wrapped lists too
@@ -1031,26 +1090,21 @@ class TQLParser:
1031
1090
  position=0,
1032
1091
  )
1033
1092
 
1034
- # For 'in' operator, left is always the value, right is field(s)
1035
- # Extract the value from left
1036
- value_extracted, value_mutators = self.ast_builder.extract_value_info(left)
1037
-
1038
- # Check if right is a list of fields
1093
+ # Check if right is a list of fields (value in [fields] syntax)
1094
+ # Note: field in [values] is now handled by field_in_values grammar rule
1095
+ # with __field_in_values__ marker, so this is only for value in [fields]
1039
1096
  if isinstance(right, list) and len(right) > 0:
1040
- # Check if all elements are fields
1041
- all_fields = True
1042
- for item in right:
1043
- if isinstance(item, list):
1044
- # This is a typed_field group
1045
- if not (len(item) >= 1 and isinstance(item[0], str)):
1046
- all_fields = False
1047
- break
1048
- elif not isinstance(item, str):
1049
- all_fields = False
1050
- break
1097
+ # Check if all elements are fields (typed_fields wrapped in lists)
1098
+ # value in [field1, field2] produces right = [['field1'], ['field2']]
1099
+ # field in ["val1", "val2"] produces right = ['val1', 'val2']
1100
+ # (but field in [values] is now handled by __field_in_values__ marker)
1101
+ is_field_list = all(
1102
+ isinstance(item, list) and len(item) >= 1 and isinstance(item[0], str) for item in right
1103
+ )
1051
1104
 
1052
- if all_fields:
1105
+ if is_field_list:
1053
1106
  # This is "value in [field1, field2, ...]" format
1107
+ value_extracted, value_mutators = self.ast_builder.extract_value_info(left)
1054
1108
  # Create an OR expression for all fields
1055
1109
  field_comparisons = []
1056
1110
  for field in right:
@@ -1083,7 +1137,11 @@ class TQLParser:
1083
1137
  }
1084
1138
  return result
1085
1139
 
1086
- # Otherwise, treat as standard "value in field" (single field)
1140
+ # For 'in' operator with single field, left is the value, right is field
1141
+ # Extract the value from left
1142
+ value_extracted, value_mutators = self.ast_builder.extract_value_info(left)
1143
+
1144
+ # Treat as standard "value in field" (single field)
1087
1145
  field_name, type_hint, field_mutators = self.ast_builder.extract_field_info(right)
1088
1146
  result = {
1089
1147
  "type": "comparison",
@@ -225,10 +225,26 @@ class TQLGrammar:
225
225
  self.field_list_item = self.typed_field
226
226
  self.field_list = Group(Suppress("[") + delimitedList(self.field_list_item) + Suppress("]"))
227
227
 
228
- # Special case for 'in' operator - always value in field(s)
228
+ # Special case for 'in' operator - value in field(s)
229
229
  self.value_in_field = Group(self.value + CaselessKeyword("in") + self.typed_field)
230
230
  self.value_in_field_list = Group(self.value + CaselessKeyword("in") + self.field_list)
231
231
 
232
+ # Field-first 'in' operator: field in [value1, value2, ...] (checks if field equals any value)
233
+ # Add a marker "__field_in_values__" to distinguish from value_in_field
234
+ self.field_in_values = Group(
235
+ self.typed_field
236
+ + CaselessKeyword("in")
237
+ + self.list_literal
238
+ + Literal("").setParseAction(lambda: "__field_in_values__")
239
+ )
240
+ self.field_not_in_values = Group(
241
+ self.typed_field
242
+ + (CaselessKeyword("not") | Literal("!"))
243
+ + CaselessKeyword("in")
244
+ + self.list_literal
245
+ + Literal("").setParseAction(lambda: "__field_not_in_values__")
246
+ )
247
+
232
248
  def _setup_special_expressions(self):
233
249
  """Set up special expressions like geo() and nslookup()."""
234
250
  # Forward declare for recursive use
@@ -455,6 +471,8 @@ class TQLGrammar:
455
471
  | self.is_not_comparison
456
472
  | self.not_between_comparison_natural
457
473
  | self.not_between_comparison_list
474
+ | self.field_not_in_values # field not in [values] - must come before std_comparison
475
+ | self.field_in_values # field in [values] - must come before std_comparison
458
476
  | self.std_comparison
459
477
  | self.between_comparison_natural
460
478
  | self.between_comparison_list