pytrilogy 0.0.3.23__py3-none-any.whl → 0.0.3.25__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.23
3
+ Version: 0.0.3.25
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.23.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=V3stjtkh3sDxf-s4ccwlCSOIsq8WO5pyE5PuQVwEsio,303
1
+ pytrilogy-0.0.3.25.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=piV4nnwA9-0AitLYz0ClSA6czFL0v_VeaN4NIfqH7xw,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,21 +13,21 @@ 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=T3rDPoWNO_pNice90WBVwPzyiHK4pg-i-5iClkWeY8s,57392
25
+ trilogy/core/models/author.py,sha256=-90COW7QY6w-g8GkjivPZqG2yNitn74e13m5KYgB1IU,73985
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=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
29
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
30
- trilogy/core/models/environment.py,sha256=RlHNrRer4p1uSQM030iwGJL82M1hMyY5p8a550XTfUI,26606
30
+ trilogy/core/models/environment.py,sha256=eB52hyb1e9fJFybP5u5tUiaNbBHGXkMdm0i2V-LUUiw,26888
31
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
@@ -37,7 +37,7 @@ trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP75
37
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
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=ogBNUDt0UEmx-F8EXGG65i6fcz2KcIz8a3gwE7YXCVw,40956
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=Jja7SxpD0QSVdDfhaGs7QN301tt6g0npc3fAGhsuVuI,60787
94
+ trilogy/parsing/parse_engine.py,sha256=Ffm3YB2jTPcA81CZTkpQ7iAwD1LP10QQpjoKk9fey4I,61849
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.23.dist-info/METADATA,sha256=ZlYR5KCt_WOZwxuAmV6P5iFW88GmieXKo48iTCHmG9c,9100
100
- pytrilogy-0.0.3.23.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
101
- pytrilogy-0.0.3.23.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
102
- pytrilogy-0.0.3.23.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
- pytrilogy-0.0.3.23.dist-info/RECORD,,
99
+ pytrilogy-0.0.3.25.dist-info/METADATA,sha256=emvDFD0vE0A1aIYfJZDD1qNZG9QnYUt2XQ7UkYeO49s,9100
100
+ pytrilogy-0.0.3.25.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
101
+ pytrilogy-0.0.3.25.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
102
+ pytrilogy-0.0.3.25.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
+ pytrilogy-0.0.3.25.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.23"
7
+ __version__ = "0.0.3.25"
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,
@@ -255,7 +255,7 @@ class Parenthetical(
255
255
  return arg_to_datatype(self.content)
256
256
 
257
257
 
258
- class Conditional(Mergeable, ConceptArgs, Namespaced, BaseModel):
258
+ class Conditional(Mergeable, ConceptArgs, Namespaced, DataTyped, BaseModel):
259
259
  left: Expr
260
260
  right: Expr
261
261
  operator: BooleanOperator
@@ -357,6 +357,11 @@ class Conditional(Mergeable, ConceptArgs, Namespaced, BaseModel):
357
357
  output += self.right.existence_arguments
358
358
  return output
359
359
 
360
+ @property
361
+ def output_datatype(self):
362
+ # a conditional is always a boolean
363
+ return DataType.BOOL
364
+
360
365
  def decompose(self):
361
366
  chunks = []
362
367
  if self.operator == BooleanOperator.AND:
@@ -545,7 +550,7 @@ class Grain(Namespaced, BaseModel):
545
550
  return self.__add__(other)
546
551
 
547
552
 
548
- class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
553
+ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
549
554
  left: Union[
550
555
  int,
551
556
  str,
@@ -745,6 +750,11 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
745
750
  output += self.right.existence_arguments
746
751
  return output
747
752
 
753
+ @property
754
+ def output_datatype(self):
755
+ # a conditional is always a boolean
756
+ return DataType.BOOL
757
+
748
758
 
749
759
  class SubselectComparison(Comparison):
750
760
  def __eq__(self, other):
@@ -1035,6 +1045,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1035
1045
  pkeys.update(parent_keys)
1036
1046
  raw_keys = pkeys
1037
1047
  # deduplicate
1048
+
1038
1049
  final_grain = Grain.from_concepts(raw_keys, environment)
1039
1050
  keys = final_grain.components
1040
1051
  return new_lineage, final_grain, keys
@@ -1400,7 +1411,7 @@ def get_basic_type(
1400
1411
  if isinstance(type, NumericType):
1401
1412
  return DataType.NUMERIC
1402
1413
  if isinstance(type, TraitDataType):
1403
- return type.type
1414
+ return get_basic_type(type.type)
1404
1415
  return type
1405
1416
 
1406
1417
 
@@ -2244,6 +2255,11 @@ class CustomType(BaseModel):
2244
2255
  name: str
2245
2256
  type: DataType
2246
2257
 
2258
+ def with_namespace(self, namespace: str) -> "CustomType":
2259
+ return CustomType.model_construct(
2260
+ name=address_with_namespace(self.name, namespace), type=self.type
2261
+ )
2262
+
2247
2263
 
2248
2264
  Expr = (
2249
2265
  MagicConstants
@@ -2284,6 +2300,7 @@ FuncArgs = (
2284
2300
  | date
2285
2301
  | datetime
2286
2302
  | MapWrapper[Any, Any]
2303
+ | TraitDataType
2287
2304
  | DataType
2288
2305
  | ListType
2289
2306
  | 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,
@@ -1873,6 +1877,10 @@ class Factory:
1873
1877
  new.gen_concept_list_caches()
1874
1878
  return new
1875
1879
 
1880
+ @build.register
1881
+ def _(self, base: TraitDataType):
1882
+ return base
1883
+
1876
1884
  @build.register
1877
1885
  def _(self, base: Datasource):
1878
1886
  local_cache: dict[str, BuildConcept] = {}
@@ -105,12 +105,15 @@ class DataType(Enum):
105
105
 
106
106
 
107
107
  class TraitDataType(BaseModel):
108
- type: DataType
108
+ type: DataType | NumericType | StructType | ListType | MapType
109
109
  traits: list[str]
110
110
 
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
@@ -131,6 +134,9 @@ class NumericType(BaseModel):
131
134
  precision: int = 20
132
135
  scale: int = 5
133
136
 
137
+ def __str__(self) -> str:
138
+ return f"Numeric({self.precision},{self.scale})"
139
+
134
140
  @property
135
141
  def data_type(self):
136
142
  return DataType.NUMERIC
@@ -353,12 +359,21 @@ def merge_datatypes(
353
359
 
354
360
  def is_compatible_datatype(left, right):
355
361
  # for unknown types, we can't make any assumptions
362
+ if all(
363
+ isinstance(x, NumericType)
364
+ or x in (DataType.INTEGER, DataType.FLOAT, DataType.NUMERIC)
365
+ for x in (left, right)
366
+ ):
367
+ return True
368
+ elif isinstance(left, NumericType) or isinstance(right, NumericType):
369
+ return False
356
370
  if right == DataType.UNKNOWN or left == DataType.UNKNOWN:
357
371
  return True
358
372
  if left == right:
359
373
  return True
360
374
  if {left, right} == {DataType.NUMERIC, DataType.FLOAT}:
361
375
  return True
376
+
362
377
  if {left, right} == {DataType.NUMERIC, DataType.INTEGER}:
363
378
  return True
364
379
  if {left, right} == {DataType.FLOAT, DataType.INTEGER}:
@@ -382,6 +397,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
382
397
  return DataType.FLOAT
383
398
  elif isinstance(arg, NumericType):
384
399
  return arg
400
+ elif isinstance(arg, TraitDataType):
401
+ return arg
385
402
  elif isinstance(arg, ListWrapper):
386
403
  return ListType(type=arg.type)
387
404
  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(
@@ -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.render_expr(e.type, cte=cte, cte_map=cte_map)
680
684
  else:
681
685
  raise ValueError(f"Unable to render type {type(e)} {e}")
682
686
 
@@ -91,6 +91,7 @@ from trilogy.core.models.core import (
91
91
  TupleWrapper,
92
92
  arg_to_datatype,
93
93
  dict_to_map_wrapper,
94
+ is_compatible_datatype,
94
95
  list_to_wrapper,
95
96
  tuple_to_wrapper,
96
97
  )
@@ -146,6 +147,8 @@ SELF_LABEL = "root"
146
147
 
147
148
  MAX_PARSE_DEPTH = 10
148
149
 
150
+ STDLIB_ROOT = Path(__file__).parent.parent
151
+
149
152
 
150
153
  @dataclass
151
154
  class WholeGrainWrapper:
@@ -443,22 +446,36 @@ class ParseToObjects(Transformer):
443
446
  def map_type(self, args) -> MapType:
444
447
  return MapType(key_type=args[0], value_type=args[1])
445
448
 
449
+ @v_args(meta=True)
446
450
  def data_type(
447
- self, args
451
+ self, meta: Meta, args
448
452
  ) -> DataType | TraitDataType | ListType | StructType | MapType | NumericType:
449
453
  resolved = args[0]
450
454
  traits = args[2:]
455
+ base: DataType | TraitDataType | ListType | StructType | MapType | NumericType
451
456
  if isinstance(resolved, StructType):
452
- return resolved
457
+ base = resolved
453
458
  elif isinstance(resolved, ListType):
454
- return resolved
459
+ base = resolved
455
460
  elif isinstance(resolved, NumericType):
456
- return resolved
461
+ base = resolved
457
462
  elif isinstance(resolved, MapType):
458
- return resolved
459
- base = DataType(args[0].lower())
463
+ base = resolved
464
+ else:
465
+ base = DataType(args[0].lower())
460
466
  if traits:
467
+ for trait in traits:
468
+ if trait not in self.environment.data_types:
469
+ raise ParseError(
470
+ f"Invalid trait (type) {trait} for {base}, line {meta.line}."
471
+ )
472
+ matched = self.environment.data_types[trait]
473
+ if not is_compatible_datatype(matched.type, base):
474
+ raise ParseError(
475
+ f"Invalid trait (type) {trait} for {base}, line {meta.line}. Trait expects type {matched.type}, has {base}"
476
+ )
461
477
  return TraitDataType(type=base, traits=traits)
478
+
462
479
  return base
463
480
 
464
481
  def array_comparison(self, args) -> ComparisonOperator:
@@ -929,9 +946,12 @@ class ParseToObjects(Transformer):
929
946
  select=args[-1],
930
947
  )
931
948
 
932
- def resolve_import_address(self, address) -> str:
933
- if isinstance(
934
- self.environment.config.import_resolver, FileSystemImportResolver
949
+ def resolve_import_address(self, address, is_stdlib: bool = False) -> str:
950
+ if (
951
+ isinstance(
952
+ self.environment.config.import_resolver, FileSystemImportResolver
953
+ )
954
+ or is_stdlib
935
955
  ):
936
956
  with open(address, "r", encoding="utf-8") as f:
937
957
  text = f.read()
@@ -939,7 +959,7 @@ class ParseToObjects(Transformer):
939
959
  lookup = address
940
960
  if lookup not in self.environment.config.import_resolver.content:
941
961
  raise ImportError(
942
- f"Unable to import file {lookup}, not found in import resolver"
962
+ f"Unable to import file {lookup}, not found in imsport resolver"
943
963
  )
944
964
  text = self.environment.config.import_resolver.content[lookup]
945
965
  else:
@@ -957,13 +977,17 @@ class ParseToObjects(Transformer):
957
977
  cache_key = args[0]
958
978
  input_path = args[0]
959
979
  path = input_path.split(".")
960
-
961
- if isinstance(
980
+ is_stdlib = False
981
+ if path[0] == "std":
982
+ is_stdlib = True
983
+ target = join(STDLIB_ROOT, *path) + ".preql"
984
+ token_lookup: Path | str = Path(target)
985
+ elif isinstance(
962
986
  self.environment.config.import_resolver, FileSystemImportResolver
963
987
  ):
964
988
  target = join(self.environment.working_path, *path) + ".preql"
965
989
  # tokens + text are cached by path
966
- token_lookup: Path | str = Path(target)
990
+ token_lookup = Path(target)
967
991
  elif isinstance(self.environment.config.import_resolver, DictImportResolver):
968
992
  target = ".".join(path)
969
993
  token_lookup = target
@@ -984,7 +1008,7 @@ class ParseToObjects(Transformer):
984
1008
  raw_tokens = self.tokens[token_lookup]
985
1009
  text = self.text_lookup[token_lookup]
986
1010
  else:
987
- text = self.resolve_import_address(target)
1011
+ text = self.resolve_import_address(target, is_stdlib)
988
1012
  self.text_lookup[token_lookup] = text
989
1013
 
990
1014
  try:
@@ -1238,8 +1262,9 @@ class ParseToObjects(Transformer):
1238
1262
  def type_declaration(self, meta: Meta, args) -> TypeDeclaration:
1239
1263
  key = args[0]
1240
1264
  datatype = args[1]
1241
- self.environment.data_types[key] = datatype
1242
- return TypeDeclaration(type=CustomType(name=key, type=datatype))
1265
+ new = CustomType(name=key, type=datatype)
1266
+ self.environment.data_types[key] = new
1267
+ return TypeDeclaration(type=new)
1243
1268
 
1244
1269
  def int_lit(self, args):
1245
1270
  return int("".join(args))