pytrilogy 0.0.3.38__tar.gz → 0.0.3.39__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.

Potentially problematic release.


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

Files changed (139) hide show
  1. {pytrilogy-0.0.3.38/pytrilogy.egg-info → pytrilogy-0.0.3.39}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_models.py +1 -1
  4. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/environment_helpers.py +59 -42
  6. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/functions.py +5 -4
  7. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/author.py +0 -3
  8. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/environment.py +11 -4
  9. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/common.py +5 -3
  10. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/parse_engine.py +25 -3
  11. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/trilogy.lark +33 -15
  12. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/LICENSE.md +0 -0
  13. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/README.md +0 -0
  14. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pyproject.toml +0 -0
  15. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pytrilogy.egg-info/SOURCES.txt +0 -0
  16. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pytrilogy.egg-info/dependency_links.txt +0 -0
  17. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pytrilogy.egg-info/entry_points.txt +0 -0
  18. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pytrilogy.egg-info/requires.txt +0 -0
  19. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/pytrilogy.egg-info/top_level.txt +0 -0
  20. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/setup.cfg +0 -0
  21. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/setup.py +0 -0
  22. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_datatypes.py +0 -0
  23. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_declarations.py +0 -0
  24. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_derived_concepts.py +0 -0
  25. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_discovery_nodes.py +0 -0
  26. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_enums.py +0 -0
  27. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_environment.py +0 -0
  28. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_executor.py +0 -0
  29. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_functions.py +0 -0
  30. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_imports.py +0 -0
  31. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_metadata.py +0 -0
  32. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_multi_join_assignments.py +0 -0
  33. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_parse_engine.py +0 -0
  34. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_parsing.py +0 -0
  35. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_partial_handling.py +0 -0
  36. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_query_processing.py +0 -0
  37. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_query_render.py +0 -0
  38. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_select.py +0 -0
  39. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_show.py +0 -0
  40. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_statements.py +0 -0
  41. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_typing.py +0 -0
  42. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_undefined_concept.py +0 -0
  43. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_user_functions.py +0 -0
  44. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/tests/test_where_clause.py +0 -0
  45. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/authoring/__init__.py +0 -0
  46. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/compiler.py +0 -0
  47. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/constants.py +0 -0
  48. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/__init__.py +0 -0
  49. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/constants.py +0 -0
  50. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/enums.py +0 -0
  51. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/env_processor.py +0 -0
  52. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/ergonomics.py +0 -0
  53. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/exceptions.py +0 -0
  54. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/graph_models.py +0 -0
  55. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/internal.py +0 -0
  56. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/__init__.py +0 -0
  57. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/build.py +0 -0
  58. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/build_environment.py +0 -0
  59. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/core.py +0 -0
  60. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/datasource.py +0 -0
  61. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/models/execute.py +0 -0
  62. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimization.py +0 -0
  63. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimizations/__init__.py +0 -0
  64. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimizations/base_optimization.py +0 -0
  65. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimizations/inline_constant.py +0 -0
  66. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimizations/inline_datasource.py +0 -0
  67. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  68. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/__init__.py +0 -0
  69. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  70. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/graph_utils.py +0 -0
  71. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/__init__.py +0 -0
  72. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  73. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/common.py +0 -0
  74. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  75. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/group_node.py +0 -0
  76. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  77. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  78. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  79. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  80. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  81. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  82. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  83. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/select_node.py +0 -0
  84. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  85. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/union_node.py +0 -0
  86. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  87. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/node_generators/window_node.py +0 -0
  88. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/__init__.py +0 -0
  89. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/base_node.py +0 -0
  90. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/filter_node.py +0 -0
  91. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/group_node.py +0 -0
  92. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/merge_node.py +0 -0
  93. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  94. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/union_node.py +0 -0
  95. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/nodes/window_node.py +0 -0
  97. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/processing/utility.py +0 -0
  98. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/query_processor.py +0 -0
  99. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/statements/__init__.py +0 -0
  100. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/statements/author.py +0 -0
  101. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/statements/build.py +0 -0
  102. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/statements/common.py +0 -0
  103. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/core/statements/execute.py +0 -0
  104. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/__init__.py +0 -0
  105. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/base.py +0 -0
  106. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/bigquery.py +0 -0
  107. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/common.py +0 -0
  108. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/config.py +0 -0
  109. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/dataframe.py +0 -0
  110. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/duckdb.py +0 -0
  111. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/enums.py +0 -0
  112. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/postgres.py +0 -0
  113. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/presto.py +0 -0
  114. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/snowflake.py +0 -0
  115. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/dialect/sql_server.py +0 -0
  116. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/engine.py +0 -0
  117. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/executor.py +0 -0
  118. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/hooks/__init__.py +0 -0
  119. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/hooks/base_hook.py +0 -0
  120. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/hooks/graph_hook.py +0 -0
  121. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/hooks/query_debugger.py +0 -0
  122. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/metadata/__init__.py +0 -0
  123. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parser.py +0 -0
  124. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/__init__.py +0 -0
  125. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/config.py +0 -0
  126. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/exceptions.py +0 -0
  127. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/helpers.py +0 -0
  128. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/parsing/render.py +0 -0
  129. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/py.typed +0 -0
  130. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/render.py +0 -0
  131. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/scripts/__init__.py +0 -0
  132. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/scripts/trilogy.py +0 -0
  133. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/__init__.py +0 -0
  134. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/date.preql +0 -0
  135. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/display.preql +0 -0
  136. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/geography.preql +0 -0
  137. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/money.preql +0 -0
  138. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/std/report.preql +0 -0
  139. {pytrilogy-0.0.3.38 → pytrilogy-0.0.3.39}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.38
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.38
3
+ Version: 0.0.3.39
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -328,7 +328,7 @@ select avg_greater_ten;
328
328
  lineage = env.concepts["avg_greater_ten"].lineage
329
329
  assert isinstance(lineage, AggregateWrapper)
330
330
  assert isinstance(
331
- env.concepts[lineage.function.concept_arguments[0]].lineage, RowsetItem
331
+ env.concepts[lineage.function.concept_arguments[0].address].lineage, RowsetItem
332
332
  )
333
333
 
334
334
 
@@ -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.38"
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):
@@ -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
@@ -773,7 +774,7 @@ class FunctionFactory:
773
774
  output_purpose = Purpose.PROPERTY
774
775
  return Function(
775
776
  operator=operator,
776
- arguments=full_args,
777
+ arguments=full_args, # type: ignore
777
778
  output_datatype=final_output_type,
778
779
  output_purpose=output_purpose,
779
780
  valid_inputs=valid_inputs,
@@ -804,7 +805,7 @@ def create_function_derived_concept(
804
805
  purpose=purpose,
805
806
  lineage=Function(
806
807
  operator=operator,
807
- arguments=arguments,
808
+ arguments=[x.reference for x in arguments],
808
809
  output_datatype=output_type,
809
810
  output_purpose=purpose,
810
811
  arg_count=len(arguments),
@@ -896,7 +897,7 @@ def Split(args: list[Concept], environment: Environment) -> Function:
896
897
  )
897
898
 
898
899
 
899
- def AttrAccess(args: list[GENERIC_ARGS], environment: Environment):
900
+ def AttrAccess(args: list[ConceptRef | str | int], environment: Environment):
900
901
  return FunctionFactory(environment).create_function(
901
902
  args=args, operator=FunctionType.ATTR_ACCESS
902
903
  )
@@ -1604,9 +1604,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1604
1604
  ] = None
1605
1605
  arguments: Sequence[FuncArgs]
1606
1606
 
1607
- def __init__(self, **kwargs):
1608
- super().__init__(**kwargs)
1609
-
1610
1607
  def __repr__(self):
1611
1608
  return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
1612
1609
 
@@ -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()
@@ -102,8 +102,10 @@ def process_function_arg(
102
102
  concept.metadata.line_number = meta.line
103
103
  environment.add_concept(concept, meta=meta)
104
104
  return concept
105
+ elif isinstance(arg, Concept):
106
+ return arg.reference
105
107
  elif isinstance(arg, ConceptRef):
106
- return environment.concepts[arg.address]
108
+ return environment.concepts[arg.address].reference
107
109
  return arg
108
110
 
109
111
 
@@ -111,8 +113,8 @@ def process_function_args(
111
113
  args,
112
114
  meta: Meta | None,
113
115
  environment: Environment,
114
- ) -> List[Concept | Function | str | int | float | date | datetime]:
115
- final: List[Concept | Function | str | int | float | date | datetime] = []
116
+ ) -> List[ConceptRef | Function | str | int | float | date | datetime]:
117
+ final: List[ConceptRef | Function | str | int | float | date | datetime] = []
116
118
  for arg in args:
117
119
  final.append(process_function_arg(arg, meta, environment))
118
120
  return final
@@ -1,6 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import date, datetime
3
3
  from enum import Enum
4
+ from logging import getLogger
4
5
  from os.path import dirname, join
5
6
  from pathlib import Path
6
7
  from re import IGNORECASE
@@ -137,6 +138,8 @@ from trilogy.parsing.common import (
137
138
  )
138
139
  from trilogy.parsing.exceptions import ParseError
139
140
 
141
+ perf_logger = getLogger("trilogy.parse.performance")
142
+
140
143
 
141
144
  class ParsePass(Enum):
142
145
  INITIAL = 1
@@ -407,6 +410,7 @@ class ParseToObjects(Transformer):
407
410
  mapping = self.environment.concepts[address]
408
411
  datatype = mapping.output_datatype
409
412
  return ConceptRef(
413
+ # this is load-bearing to handle pseudonyms
410
414
  address=mapping.address,
411
415
  metadata=Metadata(line_number=meta.line),
412
416
  datatype=datatype,
@@ -1010,6 +1014,7 @@ class ParseToObjects(Transformer):
1010
1014
  return text
1011
1015
 
1012
1016
  def import_statement(self, args: list[str]) -> ImportStatement:
1017
+ start = datetime.now()
1013
1018
  if len(args) == 2:
1014
1019
  alias = args[-1]
1015
1020
  cache_key = args[-1]
@@ -1046,9 +1051,11 @@ class ParseToObjects(Transformer):
1046
1051
  )
1047
1052
 
1048
1053
  if token_lookup in self.tokens:
1054
+ perf_logger.debug(f"\tTokens cached for {token_lookup}")
1049
1055
  raw_tokens = self.tokens[token_lookup]
1050
1056
  text = self.text_lookup[token_lookup]
1051
1057
  else:
1058
+ perf_logger.debug(f"\tTokens not cached for {token_lookup}, resolving")
1052
1059
  text = self.resolve_import_address(target, is_stdlib)
1053
1060
  self.text_lookup[token_lookup] = text
1054
1061
 
@@ -1061,12 +1068,19 @@ class ParseToObjects(Transformer):
1061
1068
  self.tokens[token_lookup] = raw_tokens
1062
1069
 
1063
1070
  if cache_lookup in self.parsed:
1071
+ perf_logger.debug(f"\tEnvironment cached for {token_lookup}")
1064
1072
  nparser = self.parsed[cache_lookup]
1065
1073
  new_env = nparser.environment
1066
1074
  if nparser.parse_pass != ParsePass.VALIDATION:
1067
1075
  # nparser.transform(raw_tokens)
1076
+ second_pass_start = datetime.now()
1068
1077
  nparser.run_second_parse_pass()
1078
+ second_pass_end = datetime.now()
1079
+ perf_logger.debug(
1080
+ f"{second_pass_end - second_pass_start} seconds | Import {alias} key ({cache_key}) second pass took {second_pass_end - second_pass_start} to parse, {len(new_env.concepts)} concepts"
1081
+ )
1069
1082
  else:
1083
+ perf_logger.debug(f"\tParsing new for {token_lookup}")
1070
1084
  try:
1071
1085
  new_env = Environment(
1072
1086
  working_path=dirname(target),
@@ -1098,6 +1112,10 @@ class ParseToObjects(Transformer):
1098
1112
  self.environment.add_import(
1099
1113
  alias, new_env, Import(alias=alias, path=parsed_path)
1100
1114
  )
1115
+ end = datetime.now()
1116
+ perf_logger.debug(
1117
+ f"{end - start} seconds | Import {alias} key ({cache_key}) took to parse, {len(new_env.concepts)} concepts"
1118
+ )
1101
1119
  return imps
1102
1120
 
1103
1121
  @v_args(meta=True)
@@ -1273,8 +1291,8 @@ class ParseToObjects(Transformer):
1273
1291
  @v_args(meta=True)
1274
1292
  def function_binding_item(self, meta: Meta, args) -> ArgBinding:
1275
1293
  if len(args) == 2:
1276
- return ArgBinding(name=args[0], default=args[1])
1277
- return ArgBinding(name=args[0], default=None)
1294
+ return ArgBinding.model_construct(name=args[0], default=args[1])
1295
+ return ArgBinding.model_construct(name=args[0], default=None)
1278
1296
 
1279
1297
  @v_args(meta=True)
1280
1298
  def raw_function(self, meta: Meta, args) -> FunctionDeclaration:
@@ -1857,7 +1875,7 @@ def parse_text(
1857
1875
  parser = ParseToObjects(
1858
1876
  environment=environment, import_keys=["root"], parse_config=parse_config
1859
1877
  )
1860
-
1878
+ start = datetime.now()
1861
1879
  try:
1862
1880
  parser.set_text(text)
1863
1881
  # disable fail on missing to allow for circular dependencies
@@ -1867,6 +1885,10 @@ def parse_text(
1867
1885
  pass_two = parser.run_second_parse_pass()
1868
1886
  output = [v for v in pass_two if v]
1869
1887
  environment.concepts.fail_on_missing = True
1888
+ end = datetime.now()
1889
+ perf_logger.debug(
1890
+ f"Parse time: {end - start} for {len(text)} characters, {len(output)} objects"
1891
+ )
1870
1892
  except VisitError as e:
1871
1893
  unpack_visit_error(e)
1872
1894
  # this will never be reached
@@ -129,7 +129,7 @@
129
129
 
130
130
  limit: "LIMIT"i /[0-9]+/
131
131
 
132
- !window_order: ("TOP"i | "BOTTOM"i)
132
+ !window_order: /TOP|BOTTOM/i
133
133
 
134
134
  window: window_order /[0-9]+/
135
135
 
@@ -139,19 +139,26 @@
139
139
 
140
140
  over_list: concept_lit ("," concept_lit )* ","?
141
141
 
142
- !ordering: ("ASC"i | "DESC"i ) ("NULLS"i ("FIRST"i | "LAST"i | "AUTO"i) )?
142
+ !ordering: /ASC|DESC/i ("NULLS"i /FIRST|LAST|AUTO/i )?
143
143
 
144
144
  order_by: "ORDER"i "BY"i order_list
145
145
 
146
146
  //WHERE STATEMENT
147
+ LOGICAL_OR: "or"i
148
+ LOGICAL_AND: "and"i
147
149
 
148
- !logical_operator: "and"i | "or"i
149
-
150
- condition_parenthetical: "(" conditional ")"
150
+ conditional: _or_condition
151
+
152
+ _or_condition: _and_condition
153
+ | (_or_condition LOGICAL_OR _and_condition)
151
154
 
152
- _condition_unit: (expr | condition_parenthetical)
155
+ _and_condition: _condition_unit
156
+ | (_and_condition LOGICAL_AND _condition_unit)
153
157
 
154
- conditional: _condition_unit (logical_operator _condition_unit)*
158
+ condition_parenthetical: "(" conditional ")"
159
+
160
+ _condition_unit: expr
161
+ | condition_parenthetical
155
162
 
156
163
  where: "WHERE"i conditional
157
164
 
@@ -159,7 +166,7 @@
159
166
 
160
167
  !array_comparison: ( ("NOT"i "IN"i) | "IN"i)
161
168
 
162
- COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=")
169
+ COMPARISON_OPERATOR: /(is\s+not|is|=|>=|<=|!=|>|<)/i
163
170
 
164
171
  comparison: expr COMPARISON_OPERATOR expr
165
172
 
@@ -185,16 +192,28 @@
185
192
  attr_access: expr "." string_lit
186
193
 
187
194
 
188
- expr: _constant_functions | window_item | filter_item | subselect_comparison | between_comparison | fgroup | aggregate_functions | unnest | union | _static_functions | literal | concept_lit | index_access | map_key_access | attr_access | parenthetical | expr_tuple | comparison | alt_like | aggregate_by | custom_function
189
-
195
+ expr: _basic_expr | _functional_expr | _operation_expr | _access_expr
196
+
197
+ # Most common/basic expressions
198
+ _basic_expr: literal | concept_lit | parenthetical | expr_tuple
199
+
200
+ # Operations and comparisons
201
+ _operation_expr: comparison | alt_like | between_comparison | subselect_comparison
202
+
203
+ # Function-like expressions
204
+ _functional_expr: _constant_functions | _static_functions | filter_item | aggregate_functions | window_item | custom_function | fgroup | unnest | union | aggregate_by
205
+
206
+ # Access patterns
207
+ _access_expr: index_access | map_key_access | attr_access
190
208
  // functions
191
209
 
192
- fadd: ("add"i "(" expr "," expr ")" ) | ( expr "+" expr )
210
+ fadd: (/add\(/ expr "," expr ")" ) | ( expr "+" expr )
193
211
  fsub: ("subtract"i "(" expr "," expr ")" ) | ( expr "-" expr )
194
212
  fmul: ("multiply"i "(" expr "," expr ")" ) | ( expr "*" expr )
195
213
  fdiv: ( "divide"i "(" expr "," expr ")") | ( expr "/" expr )
196
214
  fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")") | ( expr "%" (int_lit | concept_lit ) )
197
- fround: "round"i "(" expr "," expr ")"
215
+ _ROUND.1: "round"i "("
216
+ fround: _ROUND expr "," expr ")"
198
217
  fabs: "abs"i "(" expr ")"
199
218
  _SQRT.1: "sqrt("
200
219
  fsqrt: _SQRT expr ")"
@@ -355,9 +374,9 @@
355
374
 
356
375
  literal: null_lit | string_lit | int_lit | float_lit | bool_lit | array_lit | map_lit | struct_lit | tuple_lit
357
376
 
358
- MODIFIER: "Optional"i | "Partial"i | "Nullable"i
377
+ MODIFIER: /OPTIONAL|PARTIAL|NULLABLE/i
359
378
 
360
- SHORTHAND_MODIFIER: "~" | "?"
379
+ SHORTHAND_MODIFIER: /~|\?/
361
380
 
362
381
  struct_type: "struct"i "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
363
382
 
@@ -373,7 +392,6 @@
373
392
  PROPERTY: "property"i
374
393
  CONST: "const"i | "constant"i
375
394
  AUTO: "AUTO"i
376
-
377
395
  // meta functions
378
396
  CONCEPTS: "CONCEPTS"i
379
397
  DATASOURCES: "DATASOURCES"i
File without changes
File without changes
File without changes
File without changes