pytrilogy 0.0.3.22__py3-none-any.whl → 0.0.3.24__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.22
3
+ Version: 0.0.3.24
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.22.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=1qcH3mcGJGfd8RueLao8BkMgttF4E-nwXO82_8H_zUg,303
1
+ pytrilogy-0.0.3.24.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=lsurlEjeCDppW8zebqegPu__ETOY8b2i8vxDeuU8hPw,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
@@ -13,31 +13,31 @@ trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
14
14
  trilogy/core/enums.py,sha256=wuW667WD3mhZnXmN2VXzohseHpdlmzrfLvPtQJNdhdw,7165
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
- trilogy/core/environment_helpers.py,sha256=oOpewPwMp8xOtx2ayeeyuLNUwr-cli7UanHKot5ebNY,7627
16
+ trilogy/core/environment_helpers.py,sha256=LjB3UsLyw7TncgS8EiMGtovGU_txyUki_QK94rSsJq0,7631
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
19
- trilogy/core/functions.py,sha256=E-OZZTshUNmEMMl8nhaUDUhQkr45gqUCdPaDiBlayic,25392
19
+ trilogy/core/functions.py,sha256=U-IkBF-HEaGFXF-PgB993CVTnDFE850LHXW-HFfhttc,25391
20
20
  trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
21
21
  trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
22
22
  trilogy/core/optimization.py,sha256=xGO8piVsLrpqrx-Aid_Y56_5slSv4eZmlP64hCHRiEc,7957
23
23
  trilogy/core/query_processor.py,sha256=Do8YpdPBdsbKtl9n37hobzk8SORMGqH-e_zNNxd-BE4,19456
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- trilogy/core/models/author.py,sha256=gsn_vkm8gvnwTvzgqOL5v3X1Lx4n8xg32tRwk_9Mxnc,73494
26
- trilogy/core/models/build.py,sha256=bO1qYvuGl6LeNGgsfS6ZHAzZBR2lBPLg-QJymp9hgkU,57235
25
+ trilogy/core/models/author.py,sha256=skeALE9eDB58f3-fkJbWkmgTZfrO88fgFuYsbYnBvk0,73709
26
+ trilogy/core/models/build.py,sha256=2K-dANoFFl36yFdEDf7FDL36zM8FKSzpjLT5K2Na8Kc,57549
27
27
  trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
28
- trilogy/core/models/core.py,sha256=nb4h1HHm5_qwmUkYth4zRhEttS1EtsMZCP4vT20EEAE,10326
28
+ trilogy/core/models/core.py,sha256=vK0JXLG3dFlF6BgigXShGqkVWSTqp3oxRpdadX0HpQM,10469
29
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
30
- trilogy/core/models/environment.py,sha256=RlHNrRer4p1uSQM030iwGJL82M1hMyY5p8a550XTfUI,26606
31
- trilogy/core/models/execute.py,sha256=SG_qXK3hrorzcXi85iie1Z5FGnrSwCE4Zs9Ntj4Q1ok,34223
30
+ trilogy/core/models/environment.py,sha256=eB52hyb1e9fJFybP5u5tUiaNbBHGXkMdm0i2V-LUUiw,26888
31
+ trilogy/core/models/execute.py,sha256=KZHiovlSr_3ZjyzKD1mdBlAqnPCqFCChQkO4_4WlGtg,34224
32
32
  trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
33
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
34
34
  trilogy/core/optimizations/inline_constant.py,sha256=lvNTIXaLNkw3HseJyXyDNk5R52doLU9sIg3pmU2_S08,1332
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=pDajPY0b3fa8qgg2jR6Qj30RMu-OBLztlsVRTgGJbeU,40500
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=O-O08cGLl4XX3Faf59UlkFjsjzTU-QkzlDQ0mzY-LRE,40515
39
39
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
40
- trilogy/core/processing/utility.py,sha256=Oc5tLGeDDpzhbfo2ZcF8ex1kez-NcJDMcG2Lm5BjS4c,20548
40
+ trilogy/core/processing/utility.py,sha256=mnN_pewdpDgRou4QJ1JLcqYHyZdp8DrcsGsqW3QmA3o,20591
41
41
  trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
42
42
  trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
43
43
  trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
@@ -70,7 +70,7 @@ trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
70
70
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
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
- trilogy/dialect/base.py,sha256=QxBqbMop8l1eD36RNxSNL8XWd0mbI71FryvW1XK5NuQ,40797
73
+ trilogy/dialect/base.py,sha256=rQ1uj3Bn_O_SSQH27es9E1-Pr_Rhz3fZxe7acd6Amds,40949
74
74
  trilogy/dialect/bigquery.py,sha256=PkjFcNGZHYOe655PmJhb8a0afdFULuovqP0qQVO8m0I,2953
75
75
  trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
76
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
@@ -91,13 +91,13 @@ trilogy/parsing/common.py,sha256=99tDKrpQTk-SpyTXUqKFtm-lfmhjCOQIn25hxbQvRRg,214
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=UiTrjU6Lpp25S-wly_pNS0wUYEsZpjHExxduArtE1vQ,60602
94
+ trilogy/parsing/parse_engine.py,sha256=m1RiV2_zDH0UXovqrzjuF37M9HBSccamHcfg_rKr6Nc,61073
95
95
  trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
96
96
  trilogy/parsing/trilogy.lark,sha256=7libFS5HNiyHJYzr5_lEiY-Lpqit04_PgyIPHMZT7-w,12928
97
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
99
- pytrilogy-0.0.3.22.dist-info/METADATA,sha256=hRYKLlss7MfsGa488K07_o93KgVoI77O7QBqJ30vR4I,9100
100
- pytrilogy-0.0.3.22.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
101
- pytrilogy-0.0.3.22.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
102
- pytrilogy-0.0.3.22.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
- pytrilogy-0.0.3.22.dist-info/RECORD,,
99
+ pytrilogy-0.0.3.24.dist-info/METADATA,sha256=IHMVK1xFCKTp8ECUjxcinjhzgKmU1MJaRZw2b8JSW5w,9100
100
+ pytrilogy-0.0.3.24.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
101
+ pytrilogy-0.0.3.24.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
102
+ pytrilogy-0.0.3.24.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
+ pytrilogy-0.0.3.24.dist-info/RECORD,,
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.22"
7
+ __version__ = "0.0.3.24"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -54,7 +54,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
54
54
  grain=concept.grain,
55
55
  namespace=concept.namespace,
56
56
  keys=set(
57
- concept.address,
57
+ [concept.address],
58
58
  ),
59
59
  metadata=Metadata(
60
60
  description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}. ",
@@ -102,7 +102,7 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
102
102
  grain=concept.grain,
103
103
  namespace=concept.namespace,
104
104
  keys=set(
105
- concept.address,
105
+ [concept.address],
106
106
  ),
107
107
  metadata=Metadata(
108
108
  description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}.",
trilogy/core/functions.py CHANGED
@@ -717,7 +717,6 @@ class FunctionFactory:
717
717
  output_purpose = Purpose.METRIC
718
718
  else:
719
719
  output_purpose = Purpose.PROPERTY
720
-
721
720
  return Function(
722
721
  operator=operator,
723
722
  arguments=full_args,
@@ -1035,6 +1035,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1035
1035
  pkeys.update(parent_keys)
1036
1036
  raw_keys = pkeys
1037
1037
  # deduplicate
1038
+
1038
1039
  final_grain = Grain.from_concepts(raw_keys, environment)
1039
1040
  keys = final_grain.components
1040
1041
  return new_lineage, final_grain, keys
@@ -2244,6 +2245,11 @@ class CustomType(BaseModel):
2244
2245
  name: str
2245
2246
  type: DataType
2246
2247
 
2248
+ def with_namespace(self, namespace: str) -> "CustomType":
2249
+ return CustomType.model_construct(
2250
+ name=address_with_namespace(self.name, namespace), type=self.type
2251
+ )
2252
+
2247
2253
 
2248
2254
  Expr = (
2249
2255
  MagicConstants
@@ -2284,6 +2290,7 @@ FuncArgs = (
2284
2290
  | date
2285
2291
  | datetime
2286
2292
  | MapWrapper[Any, Any]
2293
+ | TraitDataType
2287
2294
  | DataType
2288
2295
  | ListType
2289
2296
  | MapType
@@ -77,6 +77,7 @@ from trilogy.core.models.core import (
77
77
  MapWrapper,
78
78
  NumericType,
79
79
  StructType,
80
+ TraitDataType,
80
81
  TupleWrapper,
81
82
  arg_to_datatype,
82
83
  )
@@ -1066,7 +1067,9 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1066
1067
  # class BuildFunction(Function):
1067
1068
  operator: FunctionType
1068
1069
  arg_count: int = Field(default=1)
1069
- output_datatype: DataType | ListType | StructType | MapType | NumericType
1070
+ output_datatype: (
1071
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
1072
+ )
1070
1073
  output_purpose: Purpose
1071
1074
  valid_inputs: Optional[
1072
1075
  Union[
@@ -1082,6 +1085,7 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1082
1085
  date,
1083
1086
  datetime,
1084
1087
  MapWrapper[Any, Any],
1088
+ TraitDataType,
1085
1089
  DataType,
1086
1090
  ListType,
1087
1091
  MapType,
@@ -1721,6 +1725,10 @@ class Factory:
1721
1725
  components=base.components, where_clause=where
1722
1726
  )
1723
1727
 
1728
+ @build.register
1729
+ def _(self, base: TupleWrapper) -> TupleWrapper:
1730
+ return TupleWrapper(val=[self.build(x) for x in base.val], type=base.type)
1731
+
1724
1732
  @build.register
1725
1733
  def _(self, base: FilterItem) -> BuildFilterItem:
1726
1734
  return BuildFilterItem.model_construct(
@@ -1869,6 +1877,10 @@ class Factory:
1869
1877
  new.gen_concept_list_caches()
1870
1878
  return new
1871
1879
 
1880
+ @build.register
1881
+ def _(self, base: TraitDataType):
1882
+ return base
1883
+
1872
1884
  @build.register
1873
1885
  def _(self, base: Datasource):
1874
1886
  local_cache: dict[str, BuildConcept] = {}
@@ -111,6 +111,9 @@ class TraitDataType(BaseModel):
111
111
  def __hash__(self):
112
112
  return hash(self.type)
113
113
 
114
+ def __str__(self) -> str:
115
+ return f"Trait<{self.type}, {self.traits}>"
116
+
114
117
  def __eq__(self, other):
115
118
  if isinstance(other, DataType):
116
119
  return self.type == other
@@ -382,6 +385,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
382
385
  return DataType.FLOAT
383
386
  elif isinstance(arg, NumericType):
384
387
  return arg
388
+ elif isinstance(arg, TraitDataType):
389
+ return arg
385
390
  elif isinstance(arg, ListWrapper):
386
391
  return ListType(type=arg.type)
387
392
  elif isinstance(arg, DataTyped):
@@ -442,6 +442,13 @@ class Environment(BaseModel):
442
442
  self.functions[address_with_namespace(key, alias)] = (
443
443
  function.with_namespace(alias)
444
444
  )
445
+ for key, type in source.data_types.items():
446
+ if same_namespace:
447
+ self.data_types[key] = type
448
+ else:
449
+ self.data_types[address_with_namespace(key, alias)] = (
450
+ type.with_namespace(alias)
451
+ )
445
452
  return self
446
453
 
447
454
  def add_file_import(
@@ -162,7 +162,7 @@ class CTE(BaseModel):
162
162
  base += (
163
163
  f"\n-- Nullable: {', '.join([str(x) for x in self.nullable_concepts])}."
164
164
  )
165
- base +='\n'
165
+ base += "\n"
166
166
  return base
167
167
 
168
168
  def inline_parent_datasource(
@@ -788,14 +788,16 @@ def _search_concepts(
788
788
  # if anything we need to get is in the filter set and it's a computed value
789
789
  # we need to get _everything_ in this loop
790
790
  required_filters = [
791
- x
792
- for x in mandatory_list if x.derivation not in (Derivation.ROOT, Derivation.CONSTANT)
793
- and not (x.derivation == Derivation.AGGREGATE and x.granularity == Granularity.SINGLE_ROW)
794
- and x.address in conditions.row_arguments
795
- ]
796
- if any(
797
- required_filters
798
- ):
791
+ x
792
+ for x in mandatory_list
793
+ if x.derivation not in (Derivation.ROOT, Derivation.CONSTANT)
794
+ and not (
795
+ x.derivation == Derivation.AGGREGATE
796
+ and x.granularity == Granularity.SINGLE_ROW
797
+ )
798
+ and x.address in conditions.row_arguments
799
+ ]
800
+ if any(required_filters):
799
801
  logger.info(
800
802
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} derived condition row inputs {[x.address for x in required_filters]} present in mandatory list, forcing condition evaluation at this level. "
801
803
  )
@@ -38,6 +38,7 @@ from trilogy.core.models.core import (
38
38
  MapType,
39
39
  MapWrapper,
40
40
  NumericType,
41
+ TraitDataType,
41
42
  TupleWrapper,
42
43
  )
43
44
  from trilogy.core.models.execute import (
@@ -441,6 +442,7 @@ def is_scalar_condition(
441
442
  | BuildCaseWhen
442
443
  | BuildCaseElse
443
444
  | MagicConstants
445
+ | TraitDataType
444
446
  | DataType
445
447
  | MapWrapper[Any, Any]
446
448
  | ListType
trilogy/dialect/base.py CHANGED
@@ -36,6 +36,7 @@ from trilogy.core.models.core import (
36
36
  MapWrapper,
37
37
  NumericType,
38
38
  StructType,
39
+ TraitDataType,
39
40
  TupleWrapper,
40
41
  )
41
42
  from trilogy.core.models.datasource import Datasource, RawColumnExpr
@@ -510,6 +511,7 @@ class BaseDialect:
510
511
  date,
511
512
  datetime,
512
513
  DataType,
514
+ TraitDataType,
513
515
  MagicConstants,
514
516
  MapWrapper[Any, Any],
515
517
  MapType,
@@ -677,6 +679,8 @@ class BaseDialect:
677
679
  return self.FUNCTION_MAP[FunctionType.DATE_LITERAL](e)
678
680
  elif isinstance(e, datetime):
679
681
  return self.FUNCTION_MAP[FunctionType.DATETIME_LITERAL](e)
682
+ elif isinstance(e, TraitDataType):
683
+ return self.DATATYPE_MAP.get(e.type, e.type.value)
680
684
  else:
681
685
  raise ValueError(f"Unable to render type {type(e)} {e}")
682
686
 
@@ -443,8 +443,9 @@ class ParseToObjects(Transformer):
443
443
  def map_type(self, args) -> MapType:
444
444
  return MapType(key_type=args[0], value_type=args[1])
445
445
 
446
+ @v_args(meta=True)
446
447
  def data_type(
447
- self, args
448
+ self, meta: Meta, args
448
449
  ) -> DataType | TraitDataType | ListType | StructType | MapType | NumericType:
449
450
  resolved = args[0]
450
451
  traits = args[2:]
@@ -458,7 +459,13 @@ class ParseToObjects(Transformer):
458
459
  return resolved
459
460
  base = DataType(args[0].lower())
460
461
  if traits:
462
+ for trait in traits:
463
+ if trait not in self.environment.data_types:
464
+ raise ParseError(
465
+ f"Invalid type trait {trait} for {base}, line {meta.line}"
466
+ )
461
467
  return TraitDataType(type=base, traits=traits)
468
+
462
469
  return base
463
470
 
464
471
  def array_comparison(self, args) -> ComparisonOperator:
@@ -1238,8 +1245,9 @@ class ParseToObjects(Transformer):
1238
1245
  def type_declaration(self, meta: Meta, args) -> TypeDeclaration:
1239
1246
  key = args[0]
1240
1247
  datatype = args[1]
1241
- self.environment.data_types[key] = datatype
1242
- return TypeDeclaration(type=CustomType(name=key, type=datatype))
1248
+ new = CustomType(name=key, type=datatype)
1249
+ self.environment.data_types[key] = new
1250
+ return TypeDeclaration(type=new)
1243
1251
 
1244
1252
  def int_lit(self, args):
1245
1253
  return int("".join(args))
@@ -1342,7 +1350,10 @@ class ParseToObjects(Transformer):
1342
1350
  )
1343
1351
 
1344
1352
  def expr_tuple(self, args):
1345
- return TupleWrapper(content=tuple(args))
1353
+ datatypes = set([arg_to_datatype(x) for x in args])
1354
+ if len(datatypes) != 1:
1355
+ raise ParseError("Tuple must have same type for all elements")
1356
+ return TupleWrapper(val=tuple(args), type=datatypes.pop())
1346
1357
 
1347
1358
  def parenthetical(self, args):
1348
1359
  return Parenthetical(content=args[0])