pytrilogy 0.0.3.41__py3-none-any.whl → 0.0.3.43__py3-none-any.whl

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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.41
3
+ Version: 0.0.3.43
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,5 +1,5 @@
1
- pytrilogy-0.0.3.41.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=4qLdjNZwaaPUShk24aEi2JWuFTbRXxd502rsSHiH-tk,303
1
+ pytrilogy-0.0.3.43.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=4cXB9506hb2gprGWKbo-5R8LE3DuFBSrMfMr82Gwvn4,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -23,7 +23,7 @@ trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,
23
23
  trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  trilogy/core/models/author.py,sha256=hS1caD8y7XWRBlHfwgZOrBcz3TisDPba8joFaiEXxX0,77072
26
- trilogy/core/models/build.py,sha256=Gz_1s3FZtLww6r8RdYi7MxVhOSMw-mlmUg_tDyqGGwE,61881
26
+ trilogy/core/models/build.py,sha256=NdDRcgvNoa2CHoXcPvWAUkCA2TRUKYm0zIl5NBx0sfo,61796
27
27
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
28
28
  trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
29
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
@@ -35,7 +35,7 @@ trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52d
35
35
  trilogy/core/optimizations/inline_datasource.py,sha256=AHuTGh2x0GQ8usOe0NiFncfTFQ_KogdgDl4uucmhIbI,4241
36
36
  trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
37
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- trilogy/core/processing/concept_strategies_v3.py,sha256=reXqgktFKy5qhm4k6abrs4J16Sovxd4naHrsEcNarEk,43957
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=dx6w7euQOozoNv-ugrFOHZcCSEkpG8yhK44QH5-yGt4,44046
39
39
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
40
40
  trilogy/core/processing/utility.py,sha256=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZKymUqk,21469
41
41
  trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
@@ -43,7 +43,7 @@ trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt21
43
43
  trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
44
44
  trilogy/core/processing/node_generators/filter_node.py,sha256=lT167yBgy3P9sDBM1Cjj0PKSXro8dvGtBmc8nwsUjig,8366
45
45
  trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
46
- trilogy/core/processing/node_generators/group_to_node.py,sha256=v2Xl5VjNO2hKJAdmqCbmAOqaRPvxRUbtfhhQXFMifWk,3197
46
+ trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
47
47
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
48
48
  trilogy/core/processing/node_generators/node_merge_node.py,sha256=sv55oynfqgpHEpo1OEtVDri-5fywzPhDlR85qaWikvY,16195
49
49
  trilogy/core/processing/node_generators/rowset_node.py,sha256=YmBs6ZQ7azLXRFEmeoecpGjK4pMHsUCovuBxfb3UKZI,6848
@@ -52,7 +52,7 @@ trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2Lyebj
52
52
  trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
53
53
  trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
54
54
  trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOlgn3E1-B38osng0dh2pDykwITY,2410
55
- trilogy/core/processing/node_generators/window_node.py,sha256=MjLmFKUiS-_p-Ak_9mr3becGde9eu5frxmqI7plIETY,5808
55
+ trilogy/core/processing/node_generators/window_node.py,sha256=lj94LRcJaypyfLEucQwIn65ZQsSAkYV_r1esFhPRUDc,6047
56
56
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
58
58
  trilogy/core/processing/nodes/__init__.py,sha256=Lxr3rs_bqOAtMtn3DHIkY058ZzjyLM5mSfGMKW2z0NY,5555
@@ -71,7 +71,7 @@ trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWh
71
71
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
72
72
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
74
- trilogy/dialect/bigquery.py,sha256=j5PQvwMUMcLHaxZgbqe6P-v-pwhHDQ38z8uK6ecxzR0,3359
74
+ trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
75
75
  trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
76
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
77
77
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
@@ -87,11 +87,11 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
87
87
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
88
88
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- trilogy/parsing/common.py,sha256=yZJ0b77viQktG5vi4CS8kz9KXj2K3nAb17bkDrw4-WI,26204
90
+ trilogy/parsing/common.py,sha256=2KwR86ZNH04tcs500l02jLPkFaStEYc9wtR1NVO6cYo,26411
91
91
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
92
92
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
93
93
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
94
- trilogy/parsing/parse_engine.py,sha256=Tagt3zPBViM-GZYuqHb1SnbEIwa8fx12kF07JMgmyV4,65312
94
+ trilogy/parsing/parse_engine.py,sha256=tsEBRyG2fzHUdiO1oW2mB_36vNZOfjFA4MXuf0zpF2E,65547
95
95
  trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
96
96
  trilogy/parsing/trilogy.lark,sha256=2Noe58vGYteilKd6w-np3fb4lzWI-G9Gt0AMyOMVw3k,13735
97
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +102,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
102
102
  trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
103
103
  trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
104
104
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
105
- pytrilogy-0.0.3.41.dist-info/METADATA,sha256=_O9X1If80dWqTM9fIHAyEluLAvnwvoAfKN12e1YYUJM,9100
106
- pytrilogy-0.0.3.41.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
107
- pytrilogy-0.0.3.41.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
- pytrilogy-0.0.3.41.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
- pytrilogy-0.0.3.41.dist-info/RECORD,,
105
+ pytrilogy-0.0.3.43.dist-info/METADATA,sha256=0cC502qzl6dYCgRsnS4Gi8LqcpV1Ozu7JKdYaqc69mM,9100
106
+ pytrilogy-0.0.3.43.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
107
+ pytrilogy-0.0.3.43.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
+ pytrilogy-0.0.3.43.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
+ pytrilogy-0.0.3.43.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.41"
7
+ __version__ = "0.0.3.43"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -1541,18 +1541,14 @@ class Factory:
1541
1541
  else:
1542
1542
  # constants, etc, can be ignored for group
1543
1543
  continue
1544
- group_base = group_base.model_copy(
1545
- deep=True,
1546
- update={
1547
- "lineage": AggregateWrapper(
1548
- function=group_base.lineage.function,
1549
- by=final_args,
1550
- )
1551
- },
1552
- )
1553
- group_base = group_base.with_grain(
1554
- Grain.from_concepts(final_args, environment=self.environment)
1544
+ group_base = arbitrary_to_concept(
1545
+ AggregateWrapper(
1546
+ function=group_base.lineage.function,
1547
+ by=final_args,
1548
+ ),
1549
+ environment=self.environment,
1555
1550
  )
1551
+
1556
1552
  rval = self.build(group_base)
1557
1553
  return BuildFunction.model_construct(
1558
1554
  operator=base.operator,
@@ -1650,21 +1646,20 @@ class Factory:
1650
1646
  ]
1651
1647
  else:
1652
1648
  by = [self.build(x) for x in base.by]
1649
+
1653
1650
  parent = self.build(base.function)
1654
1651
  return BuildAggregateWrapper.model_construct(function=parent, by=by)
1655
1652
 
1656
1653
  @build.register
1657
1654
  def _(self, base: ColumnAssignment) -> BuildColumnAssignment:
1658
-
1655
+ fetched = self.environment.concepts[base.concept.address]
1659
1656
  return BuildColumnAssignment.model_construct(
1660
1657
  alias=(
1661
1658
  self.build(base.alias)
1662
1659
  if isinstance(base.alias, Function)
1663
1660
  else base.alias
1664
1661
  ),
1665
- concept=self.build(
1666
- self.environment.concepts[base.concept.address].with_grain(self.grain)
1667
- ),
1662
+ concept=self.build(fetched.with_grain(self.grain)),
1668
1663
  modifiers=base.modifiers,
1669
1664
  )
1670
1665
 
@@ -1683,7 +1678,9 @@ class Factory:
1683
1678
  from trilogy.parsing.common import arbitrary_to_concept
1684
1679
 
1685
1680
  bexpr: Any
1686
- if isinstance(base.expr, AggregateWrapper):
1681
+ if isinstance(base.expr, (AggregateWrapper, WindowItem, FilterItem)) or (
1682
+ isinstance(base.expr, Function) and base.expr.operator == FunctionType.GROUP
1683
+ ):
1687
1684
  bexpr = arbitrary_to_concept(
1688
1685
  base.expr,
1689
1686
  environment=self.environment,
@@ -5,6 +5,7 @@ from typing import List, Optional, Protocol, Union
5
5
  from trilogy.constants import logger
6
6
  from trilogy.core.enums import Derivation, FunctionType, Granularity
7
7
  from trilogy.core.env_processor import generate_graph
8
+ from trilogy.core.exceptions import UnresolvableQueryException
8
9
  from trilogy.core.graph_models import ReferenceGraph
9
10
  from trilogy.core.models.author import (
10
11
  UndefinedConcept,
@@ -1120,8 +1121,8 @@ def source_query_concepts(
1120
1121
  error_strings = [
1121
1122
  f"{c.address}<{c.purpose}>{c.derivation}>" for c in output_concepts
1122
1123
  ]
1123
- raise ValueError(
1124
- f"Could not resolve connections between {error_strings} from environment graph."
1124
+ raise UnresolvableQueryException(
1125
+ f"Could not resolve connections for query with output {error_strings} from current model."
1125
1126
  )
1126
1127
  final = [x for x in root.output_concepts if x.address not in root.hidden_concepts]
1127
1128
  logger.info(
@@ -31,9 +31,9 @@ def gen_group_to_node(
31
31
  raise SyntaxError(
32
32
  f"Group to should have function lineage, is {type(concept.lineage)}"
33
33
  )
34
- group_arg = concept.lineage.arguments[0]
35
34
 
36
35
  parent_concepts: List[BuildConcept] = concept.lineage.concept_arguments
36
+ root = parent_concepts[0]
37
37
  logger.info(
38
38
  f"{padding(depth)}{LOGGER_PREFIX} group by node has required parents {[x.address for x in parent_concepts]}"
39
39
  )
@@ -47,7 +47,7 @@ def gen_group_to_node(
47
47
  conditions=conditions,
48
48
  )
49
49
  ]
50
-
50
+ outputs = parent_concepts + [concept]
51
51
  group_node = GroupNode(
52
52
  output_concepts=parent_concepts + [concept],
53
53
  input_concepts=parent_concepts,
@@ -56,17 +56,30 @@ def gen_group_to_node(
56
56
  depth=depth,
57
57
  preexisting_conditions=conditions.conditional if conditions else None,
58
58
  hidden_concepts=set(
59
- [group_arg.address]
60
- if isinstance(group_arg, BuildConcept)
61
- and group_arg.address not in local_optional
62
- else []
59
+ [
60
+ x.address
61
+ for x in outputs
62
+ if x.address not in local_optional
63
+ and x.address != concept.address
64
+ and x.address != root.address
65
+ ]
63
66
  ),
64
67
  )
65
68
 
66
69
  # early exit if no optional
67
- if not local_optional:
70
+ missing_local_option: list[BuildConcept] = [
71
+ x for x in local_optional if x not in group_node.output_concepts
72
+ ]
73
+ if not missing_local_option:
74
+ logger.info(
75
+ f"{padding(depth)}{LOGGER_PREFIX} no missing local optional required, returning group node only."
76
+ )
68
77
  return group_node
69
78
 
79
+ logger.info(
80
+ f"{padding(depth)}{LOGGER_PREFIX} group by node is missing required optional {[x.address for x in missing_local_option]}"
81
+ )
82
+
70
83
  # the keys we group by
71
84
  # are what we can use for enrichment
72
85
  enrich_node = source_concepts( # this fetches the parent + join keys
@@ -83,7 +96,9 @@ def gen_group_to_node(
83
96
  f"{padding(depth)}{LOGGER_PREFIX} group by node enrich node, returning group node only."
84
97
  )
85
98
  return group_node
86
-
99
+ logger.info(
100
+ f"{padding(depth)}{LOGGER_PREFIX} returning group to node with enrichment."
101
+ )
87
102
  return MergeNode(
88
103
  input_concepts=[concept]
89
104
  + local_optional
@@ -61,6 +61,9 @@ def gen_window_node(
61
61
  # append in keys to get the right grain
62
62
  if concept.keys:
63
63
  for item in concept.keys:
64
+ logger.info(
65
+ f"{padding(depth)}{LOGGER_PREFIX} appending search for key {item}"
66
+ )
64
67
  targets.append(environment.concepts[item])
65
68
  additional_outputs = []
66
69
  if equivalent_optional:
@@ -72,10 +75,15 @@ def gen_window_node(
72
75
  additional_outputs.append(x)
73
76
 
74
77
  grain_equivalents = [
75
- x for x in local_optional if x.keys and all([key in targets for key in x.keys])
78
+ x
79
+ for x in local_optional
80
+ if x.keys
81
+ and all([key in targets for key in x.keys])
82
+ and x.grain == concept.grain
76
83
  ]
77
84
 
78
85
  for x in grain_equivalents:
86
+ logger.info("Appending grain equivalent %s", x)
79
87
  targets.append(x)
80
88
 
81
89
  # finally, the ones we'll need to enrich
@@ -34,7 +34,7 @@ FUNCTION_MAP = {
34
34
 
35
35
  FUNCTION_GRAIN_MATCH_MAP = {
36
36
  **FUNCTION_MAP,
37
- FunctionType.COUNT: lambda args: f"{args[0]}",
37
+ FunctionType.COUNT: lambda args: "1",
38
38
  FunctionType.SUM: lambda args: f"{args[0]}",
39
39
  FunctionType.AVG: lambda args: f"{args[0]}",
40
40
  }
trilogy/parsing/common.py CHANGED
@@ -771,7 +771,13 @@ def arbitrary_to_concept(
771
771
  )
772
772
  elif isinstance(parent, Function):
773
773
  if not name:
774
- name = f"{VIRTUAL_CONCEPT_PREFIX}_func_{parent.operator.value}_{string_to_hash(str(parent))}"
774
+ if parent.operator == FunctionType.GROUP:
775
+ name = (
776
+ f"{VIRTUAL_CONCEPT_PREFIX}_group_to_{string_to_hash(str(parent))}"
777
+ )
778
+ else:
779
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_func_{parent.operator.value}_{string_to_hash(str(parent))}"
780
+
775
781
  if parent.operator == FunctionType.GROUP:
776
782
  return group_function_to_concept(
777
783
  parent,
@@ -590,6 +590,7 @@ class ParseToObjects(Transformer):
590
590
  keys=set([x.address for x in parents]),
591
591
  modifiers=modifiers,
592
592
  )
593
+
593
594
  self.environment.add_concept(concept, meta)
594
595
  return concept
595
596
 
@@ -1367,23 +1368,7 @@ class ParseToObjects(Transformer):
1367
1368
  def comparison(self, args) -> Comparison:
1368
1369
  if args[1] == ComparisonOperator.IN:
1369
1370
  raise SyntaxError
1370
- # if isinstance(args[0], AggregateWrapper):
1371
- # left_c = arbitrary_to_concept(
1372
- # args[0],
1373
- # environment=self.environment,
1374
- # )
1375
- # self.environment.add_concept(left_c)
1376
- # left = left_c.reference
1377
- # else:
1378
1371
  left = args[0]
1379
- # if isinstance(args[2], AggregateWrapper):
1380
- # right_c = arbitrary_to_concept(
1381
- # args[2],
1382
- # environment=self.environment,
1383
- # )
1384
- # self.environment.add_concept(right_c)
1385
- # right = right_c.reference
1386
- # else:
1387
1372
  right = args[2]
1388
1373
  return Comparison(left=left, right=right, operator=args[1])
1389
1374
 
@@ -1479,7 +1464,7 @@ class ParseToObjects(Transformer):
1479
1464
  return DatePart(args.value)
1480
1465
 
1481
1466
  @v_args(meta=True)
1482
- def window_item(self, meta, args) -> WindowItem:
1467
+ def window_item(self, meta: Meta, args) -> WindowItem:
1483
1468
  type: WindowType = args[0]
1484
1469
  order_by = []
1485
1470
  over = []
@@ -1501,7 +1486,10 @@ class ParseToObjects(Transformer):
1501
1486
  else:
1502
1487
  concept = arbitrary_to_concept(item, environment=self.environment)
1503
1488
  self.environment.add_concept(concept, meta=meta)
1504
- assert concept
1489
+ if not concept:
1490
+ raise ParseError(
1491
+ f"Window statements must be on fields, not constants - error in: `{self.text_lookup[self.parse_address][meta.start_pos:meta.end_pos]}`"
1492
+ )
1505
1493
  return WindowItem(
1506
1494
  type=type,
1507
1495
  content=concept.reference,
@@ -1830,16 +1818,24 @@ class ParseToObjects(Transformer):
1830
1818
  return self.function_factory.create_function(args, FunctionType.BOOL, meta)
1831
1819
 
1832
1820
 
1833
- def unpack_visit_error(e: VisitError):
1821
+ def unpack_visit_error(e: VisitError, text: str | None = None):
1834
1822
  """This is required to get exceptions from imports, which would
1835
1823
  raise nested VisitErrors"""
1836
1824
  if isinstance(e.orig_exc, VisitError):
1837
- unpack_visit_error(e.orig_exc)
1825
+ unpack_visit_error(e.orig_exc, text)
1838
1826
  elif isinstance(e.orig_exc, (UndefinedConceptException, ImportError)):
1839
1827
  raise e.orig_exc
1840
1828
  elif isinstance(e.orig_exc, (SyntaxError, TypeError)):
1841
1829
  if isinstance(e.obj, Tree):
1842
- raise InvalidSyntaxException(
1830
+ if text:
1831
+ extract = text[e.obj.meta.start_pos - 5 : e.obj.meta.end_pos + 5]
1832
+ raise InvalidSyntaxException(
1833
+ str(e.orig_exc)
1834
+ + " in "
1835
+ + str(e.rule)
1836
+ + f" Line: {e.obj.meta.line} ({extract})"
1837
+ )
1838
+ InvalidSyntaxException(
1843
1839
  str(e.orig_exc) + " in " + str(e.rule) + f" Line: {e.obj.meta.line}"
1844
1840
  )
1845
1841
  raise InvalidSyntaxException(str(e.orig_exc)).with_traceback(
@@ -1890,7 +1886,7 @@ def parse_text(
1890
1886
  f"Parse time: {end - start} for {len(text)} characters, {len(output)} objects"
1891
1887
  )
1892
1888
  except VisitError as e:
1893
- unpack_visit_error(e)
1889
+ unpack_visit_error(e, text)
1894
1890
  # this will never be reached
1895
1891
  raise e
1896
1892
  except (
@@ -1901,6 +1897,12 @@ def parse_text(
1901
1897
  ValidationError,
1902
1898
  TypeError,
1903
1899
  ) as e:
1900
+ if isinstance(
1901
+ e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput, UnexpectedToken)
1902
+ ):
1903
+ raise InvalidSyntaxException(
1904
+ str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
1905
+ )
1904
1906
  raise InvalidSyntaxException(str(e))
1905
1907
 
1906
1908
  return environment, output