pytrilogy 0.0.1.102__tar.gz → 0.0.1.104__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.
- {pytrilogy-0.0.1.102/pytrilogy.egg-info → pytrilogy-0.0.1.104}/PKG-INFO +2 -2
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/README.md +1 -1
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104/pytrilogy.egg-info}/PKG-INFO +2 -2
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_models.py +19 -5
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/env_processor.py +5 -1
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/models.py +84 -29
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/concept_strategies_v3.py +6 -4
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/filter_node.py +2 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/rowset_node.py +10 -6
- pytrilogy-0.0.1.104/trilogy/core/processing/node_generators/select_node.py +564 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/__init__.py +54 -1
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/parse_engine.py +23 -12
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/scripts/trilogy.py +1 -1
- pytrilogy-0.0.1.102/trilogy/core/processing/node_generators/select_node.py +0 -328
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/LICENSE.md +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pyproject.toml +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/setup.cfg +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/setup.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_select.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/concept_merge.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/merge_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/docs/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/utility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.104
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -27,7 +27,7 @@ Requires-Dist: sqlalchemy-bigquery; extra == "bigquery"
|
|
|
27
27
|
Provides-Extra: snowflake
|
|
28
28
|
Requires-Dist: snowflake-sqlalchemy; extra == "snowflake"
|
|
29
29
|
|
|
30
|
-
##Trilogy
|
|
30
|
+
## Trilogy
|
|
31
31
|
[](https://trilogydata.dev/)
|
|
32
32
|
[](https://discord.gg/Z4QSSuqGEd)
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.104
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -27,7 +27,7 @@ Requires-Dist: sqlalchemy-bigquery; extra == "bigquery"
|
|
|
27
27
|
Provides-Extra: snowflake
|
|
28
28
|
Requires-Dist: snowflake-sqlalchemy; extra == "snowflake"
|
|
29
29
|
|
|
30
|
-
##Trilogy
|
|
30
|
+
## Trilogy
|
|
31
31
|
[](https://trilogydata.dev/)
|
|
32
32
|
[](https://discord.gg/Z4QSSuqGEd)
|
|
33
33
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from trilogy.core.enums import BooleanOperator, Purpose, JoinType
|
|
1
|
+
from trilogy.core.enums import BooleanOperator, Purpose, JoinType, ComparisonOperator
|
|
2
2
|
from trilogy.core.models import (
|
|
3
3
|
CTE,
|
|
4
4
|
Grain,
|
|
@@ -9,6 +9,7 @@ from trilogy.core.models import (
|
|
|
9
9
|
Address,
|
|
10
10
|
UndefinedConcept,
|
|
11
11
|
BaseJoin,
|
|
12
|
+
Comparison,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
@@ -73,11 +74,17 @@ def test_conditional(test_environment, test_environment_graph):
|
|
|
73
74
|
condition_b = Conditional(
|
|
74
75
|
left=test_concept, right=test_concept, operator=BooleanOperator.AND
|
|
75
76
|
)
|
|
76
|
-
|
|
77
77
|
merged = condition_a + condition_b
|
|
78
|
-
assert merged
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
assert merged == condition_a
|
|
79
|
+
|
|
80
|
+
test_concept_two = list(test_environment.concepts.values())[-2]
|
|
81
|
+
condition_c = Conditional(
|
|
82
|
+
left=test_concept, right=test_concept_two, operator=BooleanOperator.AND
|
|
83
|
+
)
|
|
84
|
+
merged_two = condition_a + condition_c
|
|
85
|
+
assert merged_two.left == condition_a
|
|
86
|
+
assert merged_two.right == condition_c
|
|
87
|
+
assert merged_two.operator == BooleanOperator.AND
|
|
81
88
|
|
|
82
89
|
|
|
83
90
|
def test_grain(test_environment):
|
|
@@ -177,3 +184,10 @@ def test_base_join(test_environment: Environment):
|
|
|
177
184
|
exc3 = exc4
|
|
178
185
|
pass
|
|
179
186
|
assert isinstance(exc3, SyntaxError)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_comparison():
|
|
190
|
+
try:
|
|
191
|
+
Comparison(left=1, right="abc", operator=ComparisonOperator.EQ)
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
assert isinstance(exc, SyntaxError)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
from trilogy.core.graph_models import
|
|
1
|
+
from trilogy.core.graph_models import (
|
|
2
|
+
ReferenceGraph,
|
|
3
|
+
concept_to_node,
|
|
4
|
+
datasource_to_node,
|
|
5
|
+
)
|
|
2
6
|
from trilogy.core.models import Environment
|
|
3
7
|
from trilogy.core.enums import PurposeLineage
|
|
4
8
|
|
|
@@ -338,7 +338,7 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
338
338
|
|
|
339
339
|
def __eq__(self, other: object):
|
|
340
340
|
if isinstance(other, str):
|
|
341
|
-
if self.address ==
|
|
341
|
+
if self.address == other:
|
|
342
342
|
return True
|
|
343
343
|
if not isinstance(other, Concept):
|
|
344
344
|
return False
|
|
@@ -355,7 +355,7 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
355
355
|
grain = ",".join([str(c.address) for c in self.grain.components])
|
|
356
356
|
return f"{self.namespace}.{self.name}<{grain}>"
|
|
357
357
|
|
|
358
|
-
@
|
|
358
|
+
@cached_property
|
|
359
359
|
def address(self) -> str:
|
|
360
360
|
return f"{self.namespace}.{self.name}"
|
|
361
361
|
|
|
@@ -436,7 +436,8 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
436
436
|
modifiers=self.modifiers,
|
|
437
437
|
)
|
|
438
438
|
|
|
439
|
-
|
|
439
|
+
@cached_property
|
|
440
|
+
def _with_default_grain(self) -> "Concept":
|
|
440
441
|
if self.purpose == Purpose.KEY:
|
|
441
442
|
# we need to make this abstract
|
|
442
443
|
grain = Grain(components=[self.with_grain(Grain())], nested=True)
|
|
@@ -473,6 +474,9 @@ class Concept(Namespaced, SelectGrain, BaseModel):
|
|
|
473
474
|
modifiers=self.modifiers,
|
|
474
475
|
)
|
|
475
476
|
|
|
477
|
+
def with_default_grain(self) -> "Concept":
|
|
478
|
+
return self._with_default_grain
|
|
479
|
+
|
|
476
480
|
@property
|
|
477
481
|
def sources(self) -> List["Concept"]:
|
|
478
482
|
if self.lineage:
|
|
@@ -610,7 +614,7 @@ class Grain(BaseModel):
|
|
|
610
614
|
[c.name == ALL_ROWS_CONCEPT for c in self.components]
|
|
611
615
|
)
|
|
612
616
|
|
|
613
|
-
@
|
|
617
|
+
@cached_property
|
|
614
618
|
def set(self):
|
|
615
619
|
return set([c.address for c in self.components_copy])
|
|
616
620
|
|
|
@@ -1585,7 +1589,7 @@ class Datasource(Namespaced, BaseModel):
|
|
|
1585
1589
|
columns=[c.with_namespace(namespace) for c in self.columns],
|
|
1586
1590
|
)
|
|
1587
1591
|
|
|
1588
|
-
@
|
|
1592
|
+
@cached_property
|
|
1589
1593
|
def concepts(self) -> List[Concept]:
|
|
1590
1594
|
return [c.concept for c in self.columns]
|
|
1591
1595
|
|
|
@@ -1780,7 +1784,7 @@ class QueryDatasource(BaseModel):
|
|
|
1780
1784
|
|
|
1781
1785
|
@field_validator("source_map")
|
|
1782
1786
|
@classmethod
|
|
1783
|
-
def validate_source_map(cls, v, info
|
|
1787
|
+
def validate_source_map(cls, v, info: ValidationInfo):
|
|
1784
1788
|
values = info.data
|
|
1785
1789
|
expected = {c.address for c in values["output_concepts"]}.union(
|
|
1786
1790
|
c.address for c in values["input_concepts"]
|
|
@@ -1887,7 +1891,9 @@ class QueryDatasource(BaseModel):
|
|
|
1887
1891
|
else None
|
|
1888
1892
|
),
|
|
1889
1893
|
source_type=self.source_type,
|
|
1890
|
-
partial_concepts=
|
|
1894
|
+
partial_concepts=unique(
|
|
1895
|
+
self.partial_concepts + other.partial_concepts, "address"
|
|
1896
|
+
),
|
|
1891
1897
|
join_derived_concepts=self.join_derived_concepts,
|
|
1892
1898
|
force_group=self.force_group,
|
|
1893
1899
|
)
|
|
@@ -2286,8 +2292,8 @@ class EnvironmentConceptDict(dict):
|
|
|
2286
2292
|
|
|
2287
2293
|
class ImportStatement(BaseModel):
|
|
2288
2294
|
alias: str
|
|
2289
|
-
path:
|
|
2290
|
-
|
|
2295
|
+
path: Path
|
|
2296
|
+
environment: Union["Environment", None] = None
|
|
2291
2297
|
# TODO: this might result in a lot of duplication
|
|
2292
2298
|
# environment:"Environment"
|
|
2293
2299
|
|
|
@@ -2322,6 +2328,9 @@ class Environment(BaseModel):
|
|
|
2322
2328
|
version: str = Field(default_factory=get_version)
|
|
2323
2329
|
cte_name_map: Dict[str, str] = Field(default_factory=dict)
|
|
2324
2330
|
|
|
2331
|
+
materialized_concepts: List[Concept] = Field(default_factory=list)
|
|
2332
|
+
_parse_count: int = 0
|
|
2333
|
+
|
|
2325
2334
|
@classmethod
|
|
2326
2335
|
def from_file(cls, path: str | Path) -> "Environment":
|
|
2327
2336
|
with open(path, "r") as f:
|
|
@@ -2347,20 +2356,14 @@ class Environment(BaseModel):
|
|
|
2347
2356
|
f.write(self.model_dump_json())
|
|
2348
2357
|
return ppath
|
|
2349
2358
|
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
if concept.address in [x.address for x in datasource.output_concepts]:
|
|
2359
|
-
found = True
|
|
2360
|
-
break
|
|
2361
|
-
if found:
|
|
2362
|
-
output.append(concept)
|
|
2363
|
-
return output
|
|
2359
|
+
def gen_materialized_concepts(self) -> None:
|
|
2360
|
+
concrete_addresses = set()
|
|
2361
|
+
for datasource in self.datasources.values():
|
|
2362
|
+
for concept in datasource.output_concepts:
|
|
2363
|
+
concrete_addresses.add(concept.address)
|
|
2364
|
+
self.materialized_concepts = [
|
|
2365
|
+
c for c in self.concepts.values() if c.address in concrete_addresses
|
|
2366
|
+
]
|
|
2364
2367
|
|
|
2365
2368
|
def validate_concept(self, lookup: str, meta: Meta | None = None):
|
|
2366
2369
|
existing: Concept = self.concepts.get(lookup) # type: ignore
|
|
@@ -2390,12 +2393,61 @@ class Environment(BaseModel):
|
|
|
2390
2393
|
|
|
2391
2394
|
def add_import(self, alias: str, environment: Environment):
|
|
2392
2395
|
self.imports[alias] = ImportStatement(
|
|
2393
|
-
alias=alias, path=
|
|
2396
|
+
alias=alias, path=Path(environment.working_path)
|
|
2394
2397
|
)
|
|
2395
2398
|
for key, concept in environment.concepts.items():
|
|
2396
2399
|
self.concepts[f"{alias}.{key}"] = concept.with_namespace(alias)
|
|
2397
2400
|
for key, datasource in environment.datasources.items():
|
|
2398
2401
|
self.datasources[f"{alias}.{key}"] = datasource.with_namespace(alias)
|
|
2402
|
+
self.gen_materialized_concepts()
|
|
2403
|
+
return self
|
|
2404
|
+
|
|
2405
|
+
def add_file_import(self, path: str, alias: str, env: Environment | None = None):
|
|
2406
|
+
from trilogy.parsing.parse_engine import ParseToObjects, PARSER
|
|
2407
|
+
|
|
2408
|
+
apath = path.split(".")
|
|
2409
|
+
apath[-1] = apath[-1] + ".preql"
|
|
2410
|
+
|
|
2411
|
+
target: Path = Path(self.working_path, *apath)
|
|
2412
|
+
if env:
|
|
2413
|
+
self.imports[alias] = ImportStatement(
|
|
2414
|
+
alias=alias, path=target, environment=env
|
|
2415
|
+
)
|
|
2416
|
+
|
|
2417
|
+
elif alias in self.imports:
|
|
2418
|
+
current = self.imports[alias]
|
|
2419
|
+
env = self.imports[alias].environment
|
|
2420
|
+
if current.path != target:
|
|
2421
|
+
raise ImportError(
|
|
2422
|
+
f"Attempted to import {target} with alias {alias} but {alias} is already imported from {current.path}"
|
|
2423
|
+
)
|
|
2424
|
+
else:
|
|
2425
|
+
try:
|
|
2426
|
+
with open(target, "r", encoding="utf-8") as f:
|
|
2427
|
+
text = f.read()
|
|
2428
|
+
nparser = ParseToObjects(
|
|
2429
|
+
visit_tokens=True,
|
|
2430
|
+
text=text,
|
|
2431
|
+
environment=Environment(
|
|
2432
|
+
working_path=target.parent,
|
|
2433
|
+
),
|
|
2434
|
+
parse_address=str(target),
|
|
2435
|
+
)
|
|
2436
|
+
nparser.transform(PARSER.parse(text))
|
|
2437
|
+
except Exception as e:
|
|
2438
|
+
raise ImportError(
|
|
2439
|
+
f"Unable to import file {target.parent}, parsing error: {e}"
|
|
2440
|
+
)
|
|
2441
|
+
env = nparser.environment
|
|
2442
|
+
if env:
|
|
2443
|
+
for _, concept in env.concepts.items():
|
|
2444
|
+
self.add_concept(concept.with_namespace(alias))
|
|
2445
|
+
|
|
2446
|
+
for _, datasource in env.datasources.items():
|
|
2447
|
+
self.add_datasource(datasource.with_namespace(alias))
|
|
2448
|
+
imps = ImportStatement(alias=alias, path=target, environment=env)
|
|
2449
|
+
self.imports[alias] = imps
|
|
2450
|
+
return imps
|
|
2399
2451
|
|
|
2400
2452
|
def parse(
|
|
2401
2453
|
self, input: str, namespace: str | None = None, persist: bool = False
|
|
@@ -2446,21 +2498,22 @@ class Environment(BaseModel):
|
|
|
2446
2498
|
from trilogy.core.environment_helpers import generate_related_concepts
|
|
2447
2499
|
|
|
2448
2500
|
generate_related_concepts(concept, self)
|
|
2501
|
+
self.gen_materialized_concepts()
|
|
2449
2502
|
return concept
|
|
2450
2503
|
|
|
2451
2504
|
def add_datasource(
|
|
2452
2505
|
self,
|
|
2453
2506
|
datasource: Datasource,
|
|
2507
|
+
meta: Meta | None = None,
|
|
2454
2508
|
):
|
|
2455
|
-
if datasource.namespace == DEFAULT_NAMESPACE:
|
|
2456
|
-
self.datasources[datasource.name] = datasource
|
|
2457
|
-
return datasource
|
|
2458
|
-
if not datasource.namespace:
|
|
2509
|
+
if not datasource.namespace or datasource.namespace == DEFAULT_NAMESPACE:
|
|
2459
2510
|
self.datasources[datasource.name] = datasource
|
|
2511
|
+
self.gen_materialized_concepts()
|
|
2460
2512
|
return datasource
|
|
2461
2513
|
self.datasources[datasource.namespace + "." + datasource.identifier] = (
|
|
2462
2514
|
datasource
|
|
2463
2515
|
)
|
|
2516
|
+
self.gen_materialized_concepts()
|
|
2464
2517
|
return datasource
|
|
2465
2518
|
|
|
2466
2519
|
|
|
@@ -2530,7 +2583,7 @@ class Comparison(Namespaced, SelectGrain, BaseModel):
|
|
|
2530
2583
|
|
|
2531
2584
|
def __post_init__(self):
|
|
2532
2585
|
if arg_to_datatype(self.left) != arg_to_datatype(self.right):
|
|
2533
|
-
raise
|
|
2586
|
+
raise SyntaxError(
|
|
2534
2587
|
f"Cannot compare {self.left} and {self.right} of different types"
|
|
2535
2588
|
)
|
|
2536
2589
|
|
|
@@ -2704,6 +2757,8 @@ class Conditional(Namespaced, SelectGrain, BaseModel):
|
|
|
2704
2757
|
def __add__(self, other) -> "Conditional":
|
|
2705
2758
|
if other is None:
|
|
2706
2759
|
return self
|
|
2760
|
+
elif str(other) == str(self):
|
|
2761
|
+
return self
|
|
2707
2762
|
elif isinstance(other, (Comparison, Conditional, Parenthetical)):
|
|
2708
2763
|
return Conditional(left=self, right=other, operator=BooleanOperator.AND)
|
|
2709
2764
|
raise ValueError(f"Cannot add {self.__class__} and {type(other)}")
|
{pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/concept_strategies_v3.py
RENAMED
|
@@ -23,7 +23,6 @@ from trilogy.core.processing.node_generators import (
|
|
|
23
23
|
gen_window_node,
|
|
24
24
|
gen_group_node,
|
|
25
25
|
gen_basic_node,
|
|
26
|
-
gen_select_node,
|
|
27
26
|
gen_unnest_node,
|
|
28
27
|
gen_merge_node,
|
|
29
28
|
gen_group_to_node,
|
|
@@ -208,7 +207,8 @@ def generate_node(
|
|
|
208
207
|
history: History | None = None,
|
|
209
208
|
) -> StrategyNode | None:
|
|
210
209
|
# first check in case there is a materialized_concept
|
|
211
|
-
|
|
210
|
+
history = history or History()
|
|
211
|
+
candidate = history.gen_select_node(
|
|
212
212
|
concept,
|
|
213
213
|
local_optional,
|
|
214
214
|
environment,
|
|
@@ -218,6 +218,7 @@ def generate_node(
|
|
|
218
218
|
accept_partial=accept_partial,
|
|
219
219
|
accept_partial_optional=False,
|
|
220
220
|
)
|
|
221
|
+
|
|
221
222
|
if candidate:
|
|
222
223
|
return candidate
|
|
223
224
|
|
|
@@ -318,9 +319,9 @@ def generate_node(
|
|
|
318
319
|
)
|
|
319
320
|
elif concept.derivation == PurposeLineage.ROOT:
|
|
320
321
|
logger.info(
|
|
321
|
-
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating select node"
|
|
322
|
+
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating select node with optional {[x.address for x in local_optional]}"
|
|
322
323
|
)
|
|
323
|
-
return gen_select_node(
|
|
324
|
+
return history.gen_select_node(
|
|
324
325
|
concept,
|
|
325
326
|
local_optional,
|
|
326
327
|
environment,
|
|
@@ -328,6 +329,7 @@ def generate_node(
|
|
|
328
329
|
depth + 1,
|
|
329
330
|
fail_if_not_found=False,
|
|
330
331
|
accept_partial=accept_partial,
|
|
332
|
+
accept_partial_optional=True,
|
|
331
333
|
)
|
|
332
334
|
else:
|
|
333
335
|
raise ValueError(f"Unknown derivation {concept.derivation}")
|
{pytrilogy-0.0.1.102 → pytrilogy-0.0.1.104}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
@@ -35,26 +35,30 @@ def gen_rowset_node(
|
|
|
35
35
|
lineage: RowsetItem = concept.lineage
|
|
36
36
|
rowset: RowsetDerivationStatement = lineage.rowset
|
|
37
37
|
select: SelectStatement | MultiSelectStatement = lineage.rowset.select
|
|
38
|
+
if where := select.where_clause:
|
|
39
|
+
targets = select.output_components + where.conditional.concept_arguments
|
|
40
|
+
else:
|
|
41
|
+
targets = select.output_components
|
|
38
42
|
node: StrategyNode = source_concepts(
|
|
39
|
-
mandatory_list=
|
|
43
|
+
mandatory_list=targets,
|
|
40
44
|
environment=environment,
|
|
41
45
|
g=g,
|
|
42
46
|
depth=depth + 1,
|
|
43
47
|
history=history,
|
|
44
48
|
)
|
|
45
|
-
node.conditions = select.where_clause.conditional if select.where_clause else None
|
|
46
|
-
# rebuild any cached info with the new condition clause
|
|
47
|
-
node.rebuild_cache()
|
|
48
49
|
if not node:
|
|
49
50
|
logger.info(
|
|
50
51
|
f"{padding(depth)}{LOGGER_PREFIX} Cannot generate rowset node for {concept}"
|
|
51
52
|
)
|
|
52
53
|
return None
|
|
54
|
+
node.conditions = select.where_clause.conditional if select.where_clause else None
|
|
55
|
+
# rebuild any cached info with the new condition clause
|
|
56
|
+
node.rebuild_cache()
|
|
53
57
|
enrichment = set([x.address for x in local_optional])
|
|
54
58
|
rowset_relevant = [
|
|
55
59
|
x
|
|
56
60
|
for x in rowset.derived_concepts
|
|
57
|
-
if x.address == concept.address or x.address in enrichment
|
|
61
|
+
# if x.address == concept.address or x.address in enrichment
|
|
58
62
|
]
|
|
59
63
|
additional_relevant = [
|
|
60
64
|
x for x in select.output_components if x.address in enrichment
|
|
@@ -68,7 +72,7 @@ def gen_rowset_node(
|
|
|
68
72
|
for item in additional_relevant:
|
|
69
73
|
node.partial_concepts.append(item)
|
|
70
74
|
|
|
71
|
-
# assume grain to be
|
|
75
|
+
# assume grain to be output of select
|
|
72
76
|
# but don't include anything aggregate at this point
|
|
73
77
|
assert node.resolution_cache
|
|
74
78
|
node.resolution_cache.grain = concept_list_to_grain(
|