pytrilogy 0.0.3.37__py3-none-any.whl → 0.0.3.39__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.37
3
+ Version: 0.0.3.39
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.37.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=5M7wcjmeEthyEiIJM3OK2LvRgaZK8UTv_Zcf0SXA8t0,303
1
+ pytrilogy-0.0.3.39.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=sSwrSvubMxHHrZZ2-OXo7jwbQqXFn0_nVeSAW4j8vo4,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=JwbWyAHOC2xRTZe2SeEvlIGPvmC1KjcJ4uh1Po5USzQ,7380
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
- trilogy/core/environment_helpers.py,sha256=80XJrhSBd3CO7aWUc7r_oxXh6CeiR9cv0zaPyfK_uDM,8540
16
+ trilogy/core/environment_helpers.py,sha256=drsaJvwmwZ1xvyEf1mAcIekQS2k3B9AKMhMzw_X2Hbs,9741
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=bgmvWdO9x2fTZ0zjekspUb4YQ4xbVfpKYIE1wu8ftPQ,27567
19
+ trilogy/core/functions.py,sha256=4fEOGgXWDvgrJtCg_5m2Y9iWnHfLbvLQ82RkIMl_1K0,27722
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=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
23
- trilogy/core/query_processor.py,sha256=Do8YpdPBdsbKtl9n37hobzk8SORMGqH-e_zNNxd-BE4,19456
23
+ trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- trilogy/core/models/author.py,sha256=rb-OVaCVBlaE08JhWNFK2sMi9r7yGBT2QK97JE0oKJg,76950
26
- trilogy/core/models/build.py,sha256=PgTLergGdBnq2NTDqoxnGtZctw3LEOmILPzp8ldf9ws,59569
25
+ trilogy/core/models/author.py,sha256=hS1caD8y7XWRBlHfwgZOrBcz3TisDPba8joFaiEXxX0,77072
26
+ trilogy/core/models/build.py,sha256=flDLLFCR0XJ0fpUhkGg-bpDOkH915tDdXaKmrAzNIcg,61828
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
30
- trilogy/core/models/environment.py,sha256=axgk7W3STy5EIrG8fUwl2oh6WCqeBAr7PWy6EOe-_Dc,27002
30
+ trilogy/core/models/environment.py,sha256=FXQ8hOJuwF4Ul5LxNPFe1r94fN8UtI9Gx3MJwXOCiO0,27222
31
31
  trilogy/core/models/execute.py,sha256=mQm5Gydo2Ph0W7w9wm5dQEarS04PC-IKAgNVsdqOZsQ,34524
32
32
  trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
33
33
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
@@ -35,42 +35,42 @@ 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=m24X5FOOcLTCvY1MY1yUK5qFcWwokMWZ5cAFI2YN9G8,43352
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=reXqgktFKy5qhm4k6abrs4J16Sovxd4naHrsEcNarEk,43957
39
39
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
40
- trilogy/core/processing/utility.py,sha256=3tC__aT76EzcnIexZfOqCH-3WzvPiCAYWs9TBoMvGjc,20903
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
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
44
- trilogy/core/processing/node_generators/filter_node.py,sha256=rlY7TbgjJlGhahYgdCIJpJbaSREAGVJEsyUIGaA38O0,8271
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=E5bEjovSx422d_MlAUCDFdY4P2WJVp61BmWwltkhzA8,3095
46
+ trilogy/core/processing/node_generators/group_to_node.py,sha256=v2Xl5VjNO2hKJAdmqCbmAOqaRPvxRUbtfhhQXFMifWk,3197
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
50
50
  trilogy/core/processing/node_generators/select_merge_node.py,sha256=lxXhMhDKGbu67QFNbbAT-BO8gbWppIvjn_hAXpLEPe0,19953
51
51
  trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2LyebjpyHU7WWANr7nKZSS9rY7DH4Wo,1888
52
52
  trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
53
- trilogy/core/processing/node_generators/union_node.py,sha256=zuMSmgF170vzlp2BBQEhKbqUMjVl2xQDqUB82Dhv-VU,2536
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
55
  trilogy/core/processing/node_generators/window_node.py,sha256=MjLmFKUiS-_p-Ak_9mr3becGde9eu5frxmqI7plIETY,5808
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
- trilogy/core/processing/nodes/__init__.py,sha256=DqPG3Y8vl5-UTeox6hn1EE6iwPIJpsM-XeZALHSgLZQ,5058
58
+ trilogy/core/processing/nodes/__init__.py,sha256=Lxr3rs_bqOAtMtn3DHIkY058ZzjyLM5mSfGMKW2z0NY,5555
59
59
  trilogy/core/processing/nodes/base_node.py,sha256=FHrY8GsTKPuMJklOjILbhGqCt5s1nmlj62Z-molARDA,16835
60
60
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
61
61
  trilogy/core/processing/nodes/group_node.py,sha256=MUvcOg9U5J6TnWBel8eht9PdI9BfAKjUxmfjP_ZXx9o,10484
62
- trilogy/core/processing/nodes/merge_node.py,sha256=djFBJ8Sq1_crP0g7REGCbMFl4so7zPzabkPgAA1TUl4,15720
62
+ trilogy/core/processing/nodes/merge_node.py,sha256=02oWRca0ba41U6PSAB14jwnWWxoyrvxRPLwkli259SY,15865
63
63
  trilogy/core/processing/nodes/select_node_v2.py,sha256=Xyfq8lU7rP7JTAd8VV0ATDNal64n4xIBgWQsOuMe_Ak,8824
64
64
  trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ_LpvPokpZKOhx4,1425
65
65
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
66
66
  trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
67
67
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
- trilogy/core/statements/author.py,sha256=dOYX2fJ9VHZgP9RhgK7Ssaq4W5vYtMJBkeiZlwqAoDM,14726
68
+ trilogy/core/statements/author.py,sha256=rYDf9rCQ4YKEO9br1OWmOZd-51AiaDkaWYegvteJa8M,14728
69
69
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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=zeuSrurbKxM22uAZBLd6__a6a27bl8zUFk3zqTV8GO0,41361
73
+ trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
74
74
  trilogy/dialect/bigquery.py,sha256=j5PQvwMUMcLHaxZgbqe6P-v-pwhHDQ38z8uK6ecxzR0,3359
75
75
  trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
76
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
@@ -87,13 +87,13 @@ 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=0fRM9_9k3Y24oanSTgHHjVI6LSTAaxTvkaq5ESVWR5o,23541
90
+ trilogy/parsing/common.py,sha256=GXGDnlHKTzcNx8NmcUNnfyUVNOcSgajrlz4YXdOTRSM,25931
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=Ol4nGl-TnOshE9Gjbo_tis-l-x9a-6c11ZrJ3RuYjBc,63873
94
+ trilogy/parsing/parse_engine.py,sha256=Tagt3zPBViM-GZYuqHb1SnbEIwa8fx12kF07JMgmyV4,65312
95
95
  trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
96
- trilogy/parsing/trilogy.lark,sha256=VqGM8rS157hVZpOqA-1aHsrb0jS-iVlTvqrCbPyqMSY,13276
96
+ trilogy/parsing/trilogy.lark,sha256=2Noe58vGYteilKd6w-np3fb4lzWI-G9Gt0AMyOMVw3k,13735
97
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
99
99
  trilogy/std/__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.37.dist-info/METADATA,sha256=QJwr5jfw6iF9Njsd4mSH9WwvcLR9YykKvzlf9OmWuLg,9100
106
- pytrilogy-0.0.3.37.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
107
- pytrilogy-0.0.3.37.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
- pytrilogy-0.0.3.37.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
- pytrilogy-0.0.3.37.dist-info/RECORD,,
105
+ pytrilogy-0.0.3.39.dist-info/METADATA,sha256=4lI1j2Kk43ajM4gujEmJDlu0vXWN2tMPqQfudt4ZKaQ,9100
106
+ pytrilogy-0.0.3.39.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
107
+ pytrilogy-0.0.3.39.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
+ pytrilogy-0.0.3.39.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
+ pytrilogy-0.0.3.39.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (79.0.1)
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.37"
7
+ __version__ = "0.0.3.39"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -1,10 +1,10 @@
1
1
  from trilogy.constants import DEFAULT_NAMESPACE
2
2
  from trilogy.core.enums import ConceptSource, DatePart, FunctionType, Purpose
3
- from trilogy.core.functions import AttrAccess, FunctionFactory
4
- from trilogy.core.models.author import Concept, Function, Metadata
3
+ from trilogy.core.functions import AttrAccess
4
+ from trilogy.core.models.author import Concept, Function, Metadata, TraitDataType
5
5
  from trilogy.core.models.core import DataType, StructType, arg_to_datatype
6
6
  from trilogy.core.models.environment import Environment
7
- from trilogy.parsing.common import Meta, process_function_args
7
+ from trilogy.parsing.common import Meta
8
8
 
9
9
  FUNCTION_DESCRIPTION_MAPS = {
10
10
  FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
@@ -19,7 +19,6 @@ FUNCTION_DESCRIPTION_MAPS = {
19
19
 
20
20
 
21
21
  def generate_date_concepts(concept: Concept, environment: Environment):
22
- factory = FunctionFactory(environment=environment)
23
22
  if concept.metadata and concept.metadata.description:
24
23
  base_description = concept.metadata.description
25
24
  else:
@@ -28,24 +27,36 @@ def generate_date_concepts(concept: Concept, environment: Environment):
28
27
  base_line_number = concept.metadata.line_number
29
28
  else:
30
29
  base_line_number = None
31
- for ftype in [
32
- FunctionType.MONTH,
33
- FunctionType.YEAR,
34
- FunctionType.QUARTER,
35
- FunctionType.DAY,
36
- FunctionType.DAY_OF_WEEK,
37
- ]:
30
+ arg_tuples: list[tuple[FunctionType, TraitDataType]] = [
31
+ (FunctionType.MONTH, TraitDataType(type=DataType.INTEGER, traits=["month"])),
32
+ (FunctionType.YEAR, TraitDataType(type=DataType.INTEGER, traits=["year"])),
33
+ (
34
+ FunctionType.QUARTER,
35
+ TraitDataType(type=DataType.INTEGER, traits=["quarter"]),
36
+ ),
37
+ (FunctionType.DAY, TraitDataType(type=DataType.INTEGER, traits=["day"])),
38
+ (
39
+ FunctionType.DAY_OF_WEEK,
40
+ TraitDataType(type=DataType.INTEGER, traits=["day_of_week"]),
41
+ ),
42
+ ]
43
+ for ftype, dtype in arg_tuples:
38
44
  fname = ftype.name.lower()
45
+ address = concept.address + f".{fname}"
46
+ if address in environment.concepts:
47
+ continue
39
48
  default_type = (
40
49
  Purpose.CONSTANT
41
50
  if concept.purpose == Purpose.CONSTANT
42
51
  else Purpose.PROPERTY
43
52
  )
44
- function = factory.create_function(
53
+ function = Function.model_construct(
45
54
  operator=ftype,
46
- args=[concept],
55
+ arguments=[concept.reference],
56
+ output_datatype=dtype,
57
+ output_purpose=default_type,
47
58
  )
48
- new_concept = Concept(
59
+ new_concept = Concept.model_construct(
49
60
  name=f"{concept.name}.{fname}",
50
61
  datatype=function.output_datatype,
51
62
  purpose=default_type,
@@ -61,15 +72,19 @@ def generate_date_concepts(concept: Concept, environment: Environment):
61
72
  concept_source=ConceptSource.AUTO_DERIVED,
62
73
  ),
63
74
  )
64
- if new_concept.name in environment.concepts:
65
- continue
66
75
  environment.add_concept(new_concept, add_derived=False)
67
76
  for grain in [DatePart.MONTH, DatePart.YEAR]:
68
- function = factory.create_function(
77
+ address = concept.address + f".{grain.value}_start"
78
+ if address in environment.concepts:
79
+ continue
80
+ function = Function.model_construct(
69
81
  operator=FunctionType.DATE_TRUNCATE,
70
- args=[concept, grain],
82
+ arguments=[concept.reference, grain],
83
+ output_datatype=DataType.DATE,
84
+ output_purpose=default_type,
85
+ arg_count=2,
71
86
  )
72
- new_concept = Concept(
87
+ new_concept = Concept.model_construct(
73
88
  name=f"{concept.name}.{grain.value}_start",
74
89
  datatype=DataType.DATE,
75
90
  purpose=Purpose.PROPERTY,
@@ -85,13 +100,11 @@ def generate_date_concepts(concept: Concept, environment: Environment):
85
100
  concept_source=ConceptSource.AUTO_DERIVED,
86
101
  ),
87
102
  )
88
- if new_concept.name in environment.concepts:
89
- continue
103
+
90
104
  environment.add_concept(new_concept, add_derived=False)
91
105
 
92
106
 
93
107
  def generate_datetime_concepts(concept: Concept, environment: Environment):
94
- factory = FunctionFactory(environment=environment)
95
108
  if concept.metadata and concept.metadata.description:
96
109
  base_description = concept.metadata.description
97
110
  else:
@@ -100,24 +113,29 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
100
113
  base_line_number = concept.metadata.line_number
101
114
  else:
102
115
  base_line_number = None
103
- for ftype in [
104
- FunctionType.DATE,
105
- FunctionType.HOUR,
106
- FunctionType.MINUTE,
107
- FunctionType.SECOND,
108
- ]:
116
+ setup_tuples: list[tuple[FunctionType, DataType | TraitDataType]] = [
117
+ (FunctionType.DATE, DataType.DATE),
118
+ (FunctionType.HOUR, TraitDataType(type=DataType.INTEGER, traits=["hour"])),
119
+ (FunctionType.MINUTE, TraitDataType(type=DataType.INTEGER, traits=["minute"])),
120
+ (FunctionType.SECOND, TraitDataType(type=DataType.INTEGER, traits=["second"])),
121
+ ]
122
+ for ftype, datatype in setup_tuples:
109
123
  fname = ftype.name.lower()
124
+ address = concept.address + f".{fname}"
125
+ if address in environment.concepts:
126
+ continue
110
127
  default_type = (
111
128
  Purpose.CONSTANT
112
129
  if concept.purpose == Purpose.CONSTANT
113
130
  else Purpose.PROPERTY
114
131
  )
115
-
116
- const_function = factory.create_function(
132
+ const_function = Function.model_construct(
117
133
  operator=ftype,
118
- args=[concept],
134
+ arguments=[concept.reference],
135
+ output_datatype=datatype,
136
+ output_purpose=default_type,
119
137
  )
120
- new_concept = Concept(
138
+ new_concept = Concept.model_construct(
121
139
  name=f"{concept.name}.{fname}",
122
140
  datatype=const_function.output_datatype,
123
141
  purpose=default_type,
@@ -148,15 +166,18 @@ def generate_key_concepts(concept: Concept, environment: Environment):
148
166
  else:
149
167
  base_line_number = None
150
168
  for ftype in [FunctionType.COUNT]:
169
+ address = concept.address + f".{ftype.name.lower()}"
170
+ if address in environment.concepts:
171
+ continue
151
172
  fname = ftype.name.lower()
152
173
  default_type = Purpose.METRIC
153
- const_function: Function = Function(
174
+ const_function: Function = Function.model_construct(
154
175
  operator=ftype,
155
176
  output_datatype=DataType.INTEGER,
156
177
  output_purpose=default_type,
157
- arguments=[concept],
178
+ arguments=[concept.reference],
158
179
  )
159
- new_concept = Concept(
180
+ new_concept = Concept.model_construct(
160
181
  name=f"{concept.name}.{fname}",
161
182
  datatype=DataType.INTEGER,
162
183
  purpose=default_type,
@@ -172,8 +193,6 @@ def generate_key_concepts(concept: Concept, environment: Environment):
172
193
  concept_source=ConceptSource.AUTO_DERIVED,
173
194
  ),
174
195
  )
175
- if new_concept.name in environment.concepts:
176
- continue
177
196
  environment.add_concept(new_concept, add_derived=False)
178
197
 
179
198
 
@@ -199,10 +218,7 @@ def generate_related_concepts(
199
218
 
200
219
  if isinstance(concept.datatype, StructType):
201
220
  for key, value in concept.datatype.fields_map.items():
202
- args = process_function_args(
203
- [concept, key], meta=meta, environment=environment
204
- )
205
- auto = Concept(
221
+ auto = Concept.model_construct(
206
222
  name=key,
207
223
  datatype=arg_to_datatype(value),
208
224
  purpose=Purpose.PROPERTY,
@@ -212,7 +228,8 @@ def generate_related_concepts(
212
228
  and environment.namespace != DEFAULT_NAMESPACE
213
229
  else concept.name
214
230
  ),
215
- lineage=AttrAccess(args, environment=environment),
231
+ lineage=AttrAccess([concept.reference, key], environment=environment),
232
+ grain=concept.grain,
216
233
  )
217
234
  environment.add_concept(auto, meta=meta)
218
235
  if isinstance(value, Concept):
trilogy/core/functions.py CHANGED
@@ -17,6 +17,7 @@ from trilogy.core.exceptions import InvalidSyntaxException
17
17
  from trilogy.core.models.author import (
18
18
  AggregateWrapper,
19
19
  Concept,
20
+ ConceptRef,
20
21
  Function,
21
22
  Parenthetical,
22
23
  UndefinedConcept,
@@ -35,7 +36,7 @@ from trilogy.core.models.core import (
35
36
  )
36
37
  from trilogy.core.models.environment import Environment
37
38
 
38
- GENERIC_ARGS = Concept | Function | str | int | float | date | datetime
39
+ GENERIC_ARGS = Concept | ConceptRef | Function | str | int | float | date | datetime
39
40
 
40
41
 
41
42
  @dataclass
@@ -191,6 +192,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
191
192
  ),
192
193
  FunctionType.GROUP: FunctionConfig(
193
194
  arg_count=-1,
195
+ output_type_function=lambda args: get_output_type_at_index(args, 0),
194
196
  ),
195
197
  FunctionType.COUNT: FunctionConfig(
196
198
  output_purpose=Purpose.METRIC,
@@ -772,7 +774,7 @@ class FunctionFactory:
772
774
  output_purpose = Purpose.PROPERTY
773
775
  return Function(
774
776
  operator=operator,
775
- arguments=full_args,
777
+ arguments=full_args, # type: ignore
776
778
  output_datatype=final_output_type,
777
779
  output_purpose=output_purpose,
778
780
  valid_inputs=valid_inputs,
@@ -803,7 +805,7 @@ def create_function_derived_concept(
803
805
  purpose=purpose,
804
806
  lineage=Function(
805
807
  operator=operator,
806
- arguments=arguments,
808
+ arguments=[x.reference for x in arguments],
807
809
  output_datatype=output_type,
808
810
  output_purpose=purpose,
809
811
  arg_count=len(arguments),
@@ -895,7 +897,7 @@ def Split(args: list[Concept], environment: Environment) -> Function:
895
897
  )
896
898
 
897
899
 
898
- def AttrAccess(args: list[GENERIC_ARGS], environment: Environment):
900
+ def AttrAccess(args: list[ConceptRef | str | int], environment: Environment):
899
901
  return FunctionFactory(environment).create_function(
900
902
  args=args, operator=FunctionType.ATTR_ACCESS
901
903
  )
@@ -180,8 +180,14 @@ class UndefinedConcept(ConceptRef):
180
180
 
181
181
  def address_with_namespace(address: str, namespace: str) -> str:
182
182
  existing_ns = address.split(".", 1)[0]
183
+ if "." in address:
184
+ existing_name = address.split(".", 1)[1]
185
+ else:
186
+ existing_name = address
187
+ if existing_name == ALL_ROWS_CONCEPT:
188
+ return address
183
189
  if existing_ns == DEFAULT_NAMESPACE:
184
- return f"{namespace}.{address.split('.',1)[1]}"
190
+ return f"{namespace}.{existing_name}"
185
191
  return f"{namespace}.{address}"
186
192
 
187
193
 
@@ -1129,7 +1135,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1129
1135
  return self.lineage.concept_arguments if self.lineage else []
1130
1136
 
1131
1137
  @classmethod
1132
- def calculate_derivation(self, lineage, purpose):
1138
+ def calculate_derivation(self, lineage, purpose: Purpose) -> Derivation:
1133
1139
  from trilogy.core.models.build import (
1134
1140
  BuildAggregateWrapper,
1135
1141
  BuildFilterItem,
@@ -1598,9 +1604,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1598
1604
  ] = None
1599
1605
  arguments: Sequence[FuncArgs]
1600
1606
 
1601
- def __init__(self, **kwargs):
1602
- super().__init__(**kwargs)
1603
-
1604
1607
  def __repr__(self):
1605
1608
  return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
1606
1609
 
@@ -65,6 +65,7 @@ from trilogy.core.models.author import (
65
65
  RowsetLineage,
66
66
  SelectLineage,
67
67
  SubselectComparison,
68
+ UndefinedConcept,
68
69
  WhereClause,
69
70
  WindowItem,
70
71
  )
@@ -800,9 +801,6 @@ class BuildConcept(Addressable, BuildConceptArgs, DataTyped, BaseModel):
800
801
  modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
801
802
  pseudonyms: set[str] = Field(default_factory=set)
802
803
 
803
- def with_select_context(self, *args, **kwargs):
804
- return self
805
-
806
804
  @property
807
805
  def is_aggregate(self) -> bool:
808
806
  return self.build_is_aggregate
@@ -1503,7 +1501,7 @@ class Factory:
1503
1501
  return base
1504
1502
 
1505
1503
  @build.register
1506
- def _(self, base: Function) -> BuildFunction:
1504
+ def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
1507
1505
  from trilogy.parsing.common import arbitrary_to_concept
1508
1506
 
1509
1507
  raw_args: list[Concept | FuncArgs] = []
@@ -1517,6 +1515,54 @@ class Factory:
1517
1515
  raw_args.append(narg)
1518
1516
  else:
1519
1517
  raw_args.append(arg)
1518
+ if base.operator == FunctionType.GROUP:
1519
+ group_base = raw_args[0]
1520
+ final_args: List[Concept | ConceptRef] = []
1521
+ if isinstance(group_base, ConceptRef):
1522
+ if group_base.address in self.environment.concepts and not isinstance(
1523
+ self.environment.concepts[group_base.address], UndefinedConcept
1524
+ ):
1525
+ group_base = self.environment.concepts[group_base.address]
1526
+ if (
1527
+ isinstance(group_base, Concept)
1528
+ and isinstance(group_base.lineage, AggregateWrapper)
1529
+ and not group_base.lineage.by
1530
+ ):
1531
+ arguments = raw_args[1:]
1532
+ for x in arguments:
1533
+ if isinstance(x, (ConceptRef, Concept)):
1534
+ final_args.append(x)
1535
+ elif isinstance(x, (AggregateWrapper, FilterItem, WindowItem)):
1536
+ newx = arbitrary_to_concept(
1537
+ x,
1538
+ environment=self.environment,
1539
+ )
1540
+ final_args.append(newx)
1541
+ else:
1542
+ # constants, etc, can be ignored for group
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)
1555
+ )
1556
+ rval = self.build(group_base)
1557
+ return BuildFunction.model_construct(
1558
+ operator=base.operator,
1559
+ arguments=[rval, *[self.build(c) for c in raw_args[1:]]],
1560
+ output_datatype=base.output_datatype,
1561
+ output_purpose=base.output_purpose,
1562
+ valid_inputs=base.valid_inputs,
1563
+ arg_count=base.arg_count,
1564
+ )
1565
+
1520
1566
  new = BuildFunction.model_construct(
1521
1567
  operator=base.operator,
1522
1568
  arguments=[self.build(c) for c in raw_args],
@@ -1568,7 +1614,10 @@ class Factory:
1568
1614
  new_lineage, final_grain, _ = base.get_select_grain_and_keys(
1569
1615
  self.grain, self.environment
1570
1616
  )
1571
- build_lineage = self.build(new_lineage)
1617
+ if new_lineage:
1618
+ build_lineage = self.build(new_lineage)
1619
+ else:
1620
+ build_lineage = None
1572
1621
  derivation = Concept.calculate_derivation(build_lineage, base.purpose)
1573
1622
  granularity = Concept.calculate_granularity(
1574
1623
  derivation, final_grain, build_lineage
@@ -129,6 +129,9 @@ class EnvironmentConceptDict(dict):
129
129
  def __getitem__(
130
130
  self, key: str, line_no: int | None = None, file: Path | None = None
131
131
  ) -> Concept | UndefinedConceptFull:
132
+ # fast access path
133
+ if key in self.keys():
134
+ return super(EnvironmentConceptDict, self).__getitem__(key)
132
135
  if isinstance(key, ConceptRef):
133
136
  return self.__getitem__(key.address, line_no=line_no, file=file)
134
137
  try:
@@ -311,11 +314,15 @@ class Environment(BaseModel):
311
314
  f.write(self.model_dump_json())
312
315
  return ppath
313
316
 
314
- def validate_concept(self, new_concept: Concept, meta: Meta | None = None):
317
+ def validate_concept(
318
+ self, new_concept: Concept, meta: Meta | None = None
319
+ ) -> Concept | None:
315
320
  lookup = new_concept.address
321
+ if lookup not in self.concepts:
322
+ return None
316
323
  existing: Concept = self.concepts.get(lookup) # type: ignore
317
- if not existing or isinstance(existing, UndefinedConcept):
318
- return
324
+ if isinstance(existing, UndefinedConcept):
325
+ return None
319
326
 
320
327
  def handle_persist():
321
328
  deriv_lookup = (
@@ -361,7 +368,7 @@ class Environment(BaseModel):
361
368
  if existing and self.config.allow_duplicate_declaration:
362
369
  if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
363
370
  return handle_persist()
364
- return
371
+ return None
365
372
  elif existing.metadata:
366
373
  if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
367
374
  return handle_persist()
@@ -110,11 +110,14 @@ def get_priority_concept(
110
110
  depth: int,
111
111
  ) -> BuildConcept:
112
112
  # optimized search for missing concepts
113
- pass_one = [
114
- c
115
- for c in all_concepts
116
- if c.address not in attempted_addresses and c.address not in found_concepts
117
- ]
113
+ pass_one = sorted(
114
+ [
115
+ c
116
+ for c in all_concepts
117
+ if c.address not in attempted_addresses and c.address not in found_concepts
118
+ ],
119
+ key=lambda x: x.address,
120
+ )
118
121
  # sometimes we need to scan intermediate concepts to get merge keys or filter keys,
119
122
  # so do an exhaustive search
120
123
  # pass_two = [c for c in all_concepts+filter_only if c.address not in attempted_addresses]
@@ -388,6 +391,9 @@ def generate_node(
388
391
  # conditions=conditions,
389
392
  )
390
393
  else:
394
+ logger.info(
395
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping search, already in a recursion fot these concepts"
396
+ )
391
397
  return None
392
398
  return ConstantNode(
393
399
  input_concepts=[],
@@ -453,9 +459,10 @@ def generate_node(
453
459
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} including any filters, there are non-root concepts we should expand first: {non_root}. Recursing with all of these as mandatory"
454
460
  )
455
461
 
456
- if not history.check_started(
457
- root_targets, accept_partial=accept_partial, conditions=conditions
458
- ):
462
+ # if not history.check_started(
463
+ # root_targets, accept_partial=accept_partial, conditions=conditions
464
+ # ) or 1==1:
465
+ if True:
459
466
  history.log_start(
460
467
  root_targets, accept_partial=accept_partial, conditions=conditions
461
468
  )
@@ -470,7 +477,10 @@ def generate_node(
470
477
  # which we do whenever we hit a root node
471
478
  # conditions=conditions,
472
479
  )
473
-
480
+ else:
481
+ logger.info(
482
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping root search, already in a recursion for these concepts"
483
+ )
474
484
  check = history.gen_select_node(
475
485
  concept,
476
486
  local_optional,
@@ -546,7 +556,10 @@ def generate_node(
546
556
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} resolved concepts through synonyms"
547
557
  )
548
558
  return resolved
549
-
559
+ else:
560
+ logger.info(
561
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} skipping synonym search, already in a recursion for these concepts"
562
+ )
550
563
  return None
551
564
  else:
552
565
  raise ValueError(f"Unknown derivation {concept.derivation} on {concept}")
@@ -1041,7 +1054,6 @@ def _search_concepts(
1041
1054
  environment=environment,
1042
1055
  depth=depth,
1043
1056
  )
1044
- logger.info(f"gcheck result is {result}")
1045
1057
  if result.required:
1046
1058
  logger.info(
1047
1059
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Adding group node"
@@ -1104,7 +1116,6 @@ def source_query_concepts(
1104
1116
  history=history,
1105
1117
  conditions=conditions,
1106
1118
  )
1107
-
1108
1119
  if not root:
1109
1120
  error_strings = [
1110
1121
  f"{c.address}<{c.purpose}>{c.derivation}>" for c in output_concepts
@@ -1130,5 +1141,4 @@ def source_query_concepts(
1130
1141
  )
1131
1142
  else:
1132
1143
  candidate = root
1133
-
1134
1144
  return candidate