pytrilogy 0.0.3.70__tar.gz → 0.0.3.72__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.3.70/pytrilogy.egg-info → pytrilogy-0.0.3.72}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pytrilogy.egg-info/SOURCES.txt +1 -0
- pytrilogy-0.0.3.72/tests/test_execute_models.py +61 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/enums.py +1 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/functions.py +5 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/graph_models.py +6 -2
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/build.py +35 -19
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/datasource.py +5 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/execute.py +38 -39
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/concept_strategies_v3.py +6 -4
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/node_merge_node.py +3 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/rowset_node.py +0 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +69 -20
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/select_merge_node.py +70 -33
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/window_node.py +2 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/base_node.py +16 -12
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/union_node.py +9 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/query_processor.py +5 -3
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/base.py +5 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/common.py +10 -16
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/parse_engine.py +9 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/net.preql +2 -1
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/README.md +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/setup.cfg +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/setup.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_parsing.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_parsing_failures.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/author.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/discovery_loop.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/discovery_node_factory.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/discovery_utility.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/discovery_validation.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/recursive_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/utility.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/parsing/trilogy.lark +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/ranking.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/utility.py +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from pytest import raises
|
|
2
|
+
|
|
3
|
+
from trilogy.core.exceptions import InvalidSyntaxException
|
|
4
|
+
from trilogy.core.models.execute import (
|
|
5
|
+
BuildConcept,
|
|
6
|
+
BuildDatasource,
|
|
7
|
+
BuildGrain,
|
|
8
|
+
DataType,
|
|
9
|
+
Purpose,
|
|
10
|
+
raise_helpful_join_validation_error,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_raise_helpful_join_validation_error():
|
|
15
|
+
|
|
16
|
+
with raises(InvalidSyntaxException):
|
|
17
|
+
raise_helpful_join_validation_error(
|
|
18
|
+
concepts=[
|
|
19
|
+
BuildConcept(
|
|
20
|
+
name="test_concept",
|
|
21
|
+
datatype=DataType.INTEGER,
|
|
22
|
+
purpose=Purpose.KEY,
|
|
23
|
+
build_is_aggregate=False,
|
|
24
|
+
grain=BuildGrain(),
|
|
25
|
+
)
|
|
26
|
+
],
|
|
27
|
+
left_datasource=BuildDatasource(name="left_ds", columns=[], address="agsg"),
|
|
28
|
+
right_datasource=BuildDatasource(
|
|
29
|
+
name="right_ds", columns=[], address="agsg"
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
with raises(InvalidSyntaxException):
|
|
33
|
+
raise_helpful_join_validation_error(
|
|
34
|
+
concepts=[
|
|
35
|
+
BuildConcept(
|
|
36
|
+
name="test_concept",
|
|
37
|
+
datatype=DataType.INTEGER,
|
|
38
|
+
purpose=Purpose.KEY,
|
|
39
|
+
build_is_aggregate=False,
|
|
40
|
+
grain=BuildGrain(),
|
|
41
|
+
)
|
|
42
|
+
],
|
|
43
|
+
left_datasource=None,
|
|
44
|
+
right_datasource=BuildDatasource(
|
|
45
|
+
name="right_ds", columns=[], address="agsg"
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
with raises(InvalidSyntaxException):
|
|
49
|
+
raise_helpful_join_validation_error(
|
|
50
|
+
concepts=[
|
|
51
|
+
BuildConcept(
|
|
52
|
+
name="test_concept",
|
|
53
|
+
datatype=DataType.INTEGER,
|
|
54
|
+
purpose=Purpose.KEY,
|
|
55
|
+
build_is_aggregate=False,
|
|
56
|
+
grain=BuildGrain(),
|
|
57
|
+
)
|
|
58
|
+
],
|
|
59
|
+
left_datasource=BuildDatasource(name="left_ds", columns=[], address="agsg"),
|
|
60
|
+
right_datasource=None,
|
|
61
|
+
)
|
|
@@ -704,6 +704,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
704
704
|
output_purpose=Purpose.CONSTANT,
|
|
705
705
|
arg_count=1,
|
|
706
706
|
),
|
|
707
|
+
FunctionType.TYPED_CONSTANT: FunctionConfig(
|
|
708
|
+
output_purpose=Purpose.CONSTANT,
|
|
709
|
+
output_type_function=get_cast_output_type,
|
|
710
|
+
arg_count=2,
|
|
711
|
+
),
|
|
707
712
|
FunctionType.IS_NULL: FunctionConfig(
|
|
708
713
|
output_purpose=Purpose.PROPERTY,
|
|
709
714
|
output_type=DataType.BOOL,
|
|
@@ -4,7 +4,7 @@ from trilogy.core.models.build import BuildConcept, BuildDatasource, BuildWhereC
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def get_graph_exact_match(
|
|
7
|
-
g: nx.DiGraph, conditions: BuildWhereClause | None
|
|
7
|
+
g: nx.DiGraph, accept_partial: bool, conditions: BuildWhereClause | None
|
|
8
8
|
) -> set[str]:
|
|
9
9
|
datasources: dict[str, BuildDatasource | list[BuildDatasource]] = (
|
|
10
10
|
nx.get_node_attributes(g, "datasource")
|
|
@@ -20,6 +20,9 @@ def get_graph_exact_match(
|
|
|
20
20
|
if not conditions and not ds.non_partial_for:
|
|
21
21
|
exact.add(node)
|
|
22
22
|
continue
|
|
23
|
+
elif not conditions and accept_partial and ds.non_partial_for:
|
|
24
|
+
exact.add(node)
|
|
25
|
+
continue
|
|
23
26
|
elif conditions:
|
|
24
27
|
if not ds.non_partial_for:
|
|
25
28
|
continue
|
|
@@ -34,10 +37,11 @@ def get_graph_exact_match(
|
|
|
34
37
|
|
|
35
38
|
def prune_sources_for_conditions(
|
|
36
39
|
g: nx.DiGraph,
|
|
40
|
+
accept_partial: bool,
|
|
37
41
|
conditions: BuildWhereClause | None,
|
|
38
42
|
):
|
|
39
43
|
|
|
40
|
-
complete = get_graph_exact_match(g, conditions)
|
|
44
|
+
complete = get_graph_exact_match(g, accept_partial, conditions)
|
|
41
45
|
to_remove = []
|
|
42
46
|
for node in g.nodes:
|
|
43
47
|
if node.startswith("ds~") and node not in complete:
|
|
@@ -1486,6 +1486,17 @@ def get_canonical_pseudonyms(environment: Environment) -> dict[str, set[str]]:
|
|
|
1486
1486
|
return roots
|
|
1487
1487
|
|
|
1488
1488
|
|
|
1489
|
+
def requires_concept_nesting(
|
|
1490
|
+
expr,
|
|
1491
|
+
) -> AggregateWrapper | WindowItem | FilterItem | Function | None:
|
|
1492
|
+
if isinstance(expr, (AggregateWrapper, WindowItem, FilterItem)):
|
|
1493
|
+
return expr
|
|
1494
|
+
if isinstance(expr, Function) and expr.operator == FunctionType.GROUP:
|
|
1495
|
+
# group by requires nesting
|
|
1496
|
+
return expr
|
|
1497
|
+
return None
|
|
1498
|
+
|
|
1499
|
+
|
|
1489
1500
|
class Factory:
|
|
1490
1501
|
|
|
1491
1502
|
def __init__(
|
|
@@ -1509,11 +1520,12 @@ class Factory:
|
|
|
1509
1520
|
| WindowItem
|
|
1510
1521
|
| FilterItem
|
|
1511
1522
|
| Function
|
|
1512
|
-
| ListWrapper
|
|
1513
|
-
| MapWrapper
|
|
1523
|
+
| ListWrapper
|
|
1524
|
+
| MapWrapper
|
|
1514
1525
|
| int
|
|
1515
1526
|
| float
|
|
1516
1527
|
| str
|
|
1528
|
+
| date
|
|
1517
1529
|
),
|
|
1518
1530
|
) -> tuple[Concept, BuildConcept]:
|
|
1519
1531
|
from trilogy.parsing.common import arbitrary_to_concept
|
|
@@ -1572,7 +1584,8 @@ class Factory:
|
|
|
1572
1584
|
|
|
1573
1585
|
raw_args: list[Concept | FuncArgs] = []
|
|
1574
1586
|
for arg in base.arguments:
|
|
1575
|
-
# to do proper discovery, we need to inject virtual intermediate
|
|
1587
|
+
# to do proper discovery, we need to inject virtual intermediate concepts
|
|
1588
|
+
# we don't use requires_concept_nesting here by design
|
|
1576
1589
|
if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1577
1590
|
narg, _ = self.instantiate_concept(arg)
|
|
1578
1591
|
raw_args.append(narg)
|
|
@@ -1640,11 +1653,10 @@ class Factory:
|
|
|
1640
1653
|
def _(self, base: CaseWhen) -> BuildCaseWhen:
|
|
1641
1654
|
|
|
1642
1655
|
comparison = base.comparison
|
|
1643
|
-
if isinstance(comparison, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1644
|
-
comparison, _ = self.instantiate_concept(comparison)
|
|
1645
1656
|
expr: Concept | FuncArgs = base.expr
|
|
1646
|
-
|
|
1647
|
-
|
|
1657
|
+
validation = requires_concept_nesting(expr)
|
|
1658
|
+
if validation:
|
|
1659
|
+
expr, _ = self.instantiate_concept(validation)
|
|
1648
1660
|
return BuildCaseWhen.model_construct(
|
|
1649
1661
|
comparison=self.build(comparison),
|
|
1650
1662
|
expr=self.build(expr),
|
|
@@ -1653,8 +1665,9 @@ class Factory:
|
|
|
1653
1665
|
@build.register
|
|
1654
1666
|
def _(self, base: CaseElse) -> BuildCaseElse:
|
|
1655
1667
|
expr: Concept | FuncArgs = base.expr
|
|
1656
|
-
|
|
1657
|
-
|
|
1668
|
+
validation = requires_concept_nesting(expr)
|
|
1669
|
+
if validation:
|
|
1670
|
+
expr, _ = self.instantiate_concept(validation)
|
|
1658
1671
|
return BuildCaseElse.model_construct(expr=self.build(expr))
|
|
1659
1672
|
|
|
1660
1673
|
@build.register
|
|
@@ -1753,10 +1766,9 @@ class Factory:
|
|
|
1753
1766
|
def _(self, base: OrderItem) -> BuildOrderItem:
|
|
1754
1767
|
|
|
1755
1768
|
bexpr: Any
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
bexpr, _ = self.instantiate_concept(base.expr)
|
|
1769
|
+
validation = requires_concept_nesting(base.expr)
|
|
1770
|
+
if validation:
|
|
1771
|
+
bexpr, _ = self.instantiate_concept(validation)
|
|
1760
1772
|
else:
|
|
1761
1773
|
bexpr = base.expr
|
|
1762
1774
|
return BuildOrderItem.model_construct(
|
|
@@ -1781,8 +1793,9 @@ class Factory:
|
|
|
1781
1793
|
def _(self, base: WindowItem) -> BuildWindowItem:
|
|
1782
1794
|
|
|
1783
1795
|
content: Concept | FuncArgs = base.content
|
|
1784
|
-
|
|
1785
|
-
|
|
1796
|
+
validation = requires_concept_nesting(base.content)
|
|
1797
|
+
if validation:
|
|
1798
|
+
content, _ = self.instantiate_concept(validation)
|
|
1786
1799
|
final_by = []
|
|
1787
1800
|
for x in base.order_by:
|
|
1788
1801
|
if (
|
|
@@ -1811,6 +1824,7 @@ class Factory:
|
|
|
1811
1824
|
@build.register
|
|
1812
1825
|
def _(self, base: SubselectComparison) -> BuildSubselectComparison:
|
|
1813
1826
|
right: Any = base.right
|
|
1827
|
+
# this has specialized logic - include all Functions
|
|
1814
1828
|
if isinstance(base.right, (AggregateWrapper, WindowItem, FilterItem, Function)):
|
|
1815
1829
|
right_c, _ = self.instantiate_concept(base.right)
|
|
1816
1830
|
right = right_c
|
|
@@ -1824,12 +1838,14 @@ class Factory:
|
|
|
1824
1838
|
def _(self, base: Comparison) -> BuildComparison:
|
|
1825
1839
|
|
|
1826
1840
|
left = base.left
|
|
1827
|
-
|
|
1828
|
-
|
|
1841
|
+
validation = requires_concept_nesting(base.left)
|
|
1842
|
+
if validation:
|
|
1843
|
+
left_c, _ = self.instantiate_concept(validation)
|
|
1829
1844
|
left = left_c # type: ignore
|
|
1830
1845
|
right = base.right
|
|
1831
|
-
|
|
1832
|
-
|
|
1846
|
+
validation = requires_concept_nesting(base.right)
|
|
1847
|
+
if validation:
|
|
1848
|
+
right_c, _ = self.instantiate_concept(validation)
|
|
1833
1849
|
right = right_c # type: ignore
|
|
1834
1850
|
return BuildComparison.model_construct(
|
|
1835
1851
|
left=self.handle_constant(self.build(left)),
|
|
@@ -249,6 +249,11 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
249
249
|
address=self.address,
|
|
250
250
|
columns=[c.with_namespace(namespace) for c in self.columns],
|
|
251
251
|
where=self.where.with_namespace(namespace) if self.where else None,
|
|
252
|
+
non_partial_for=(
|
|
253
|
+
self.non_partial_for.with_namespace(namespace)
|
|
254
|
+
if self.non_partial_for
|
|
255
|
+
else None
|
|
256
|
+
),
|
|
252
257
|
)
|
|
253
258
|
return new
|
|
254
259
|
|
|
@@ -24,12 +24,12 @@ from trilogy.core.enums import (
|
|
|
24
24
|
ComparisonOperator,
|
|
25
25
|
Derivation,
|
|
26
26
|
FunctionType,
|
|
27
|
-
Granularity,
|
|
28
27
|
JoinType,
|
|
29
28
|
Modifier,
|
|
30
29
|
Purpose,
|
|
31
30
|
SourceType,
|
|
32
31
|
)
|
|
32
|
+
from trilogy.core.exceptions import InvalidSyntaxException
|
|
33
33
|
from trilogy.core.models.build import (
|
|
34
34
|
BuildCaseElse,
|
|
35
35
|
BuildCaseWhen,
|
|
@@ -438,7 +438,7 @@ class ConceptPair(BaseModel):
|
|
|
438
438
|
|
|
439
439
|
|
|
440
440
|
class CTEConceptPair(ConceptPair):
|
|
441
|
-
cte: CTE
|
|
441
|
+
cte: CTE | UnionCTE
|
|
442
442
|
|
|
443
443
|
|
|
444
444
|
class InstantiatedUnnestJoin(BaseModel):
|
|
@@ -460,6 +460,30 @@ class UnnestJoin(BaseModel):
|
|
|
460
460
|
return self.alias + "".join([str(s.address) for s in self.concepts])
|
|
461
461
|
|
|
462
462
|
|
|
463
|
+
def raise_helpful_join_validation_error(
|
|
464
|
+
concepts: List[BuildConcept],
|
|
465
|
+
left_datasource: BuildDatasource | QueryDatasource | None,
|
|
466
|
+
right_datasource: BuildDatasource | QueryDatasource | None,
|
|
467
|
+
):
|
|
468
|
+
|
|
469
|
+
if not left_datasource or not right_datasource:
|
|
470
|
+
raise InvalidSyntaxException(
|
|
471
|
+
"No mutual keys found, and not two valid datasources"
|
|
472
|
+
)
|
|
473
|
+
left_keys = [c.address for c in left_datasource.output_concepts]
|
|
474
|
+
right_keys = [c.address for c in right_datasource.output_concepts]
|
|
475
|
+
match_concepts = [c.address for c in concepts]
|
|
476
|
+
assert left_datasource
|
|
477
|
+
assert right_datasource
|
|
478
|
+
raise InvalidSyntaxException(
|
|
479
|
+
"No mutual join keys found between"
|
|
480
|
+
f" {left_datasource.identifier} and"
|
|
481
|
+
f" {right_datasource.identifier}, left_keys {left_keys},"
|
|
482
|
+
f" right_keys {right_keys},"
|
|
483
|
+
f" provided join concepts {match_concepts}"
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
463
487
|
class BaseJoin(BaseModel):
|
|
464
488
|
right_datasource: Union[BuildDatasource, "QueryDatasource"]
|
|
465
489
|
join_type: JoinType
|
|
@@ -479,27 +503,24 @@ class BaseJoin(BaseModel):
|
|
|
479
503
|
)
|
|
480
504
|
|
|
481
505
|
# Early returns maintained as in original code
|
|
482
|
-
if self.concept_pairs:
|
|
483
|
-
return self
|
|
484
|
-
|
|
485
|
-
if self.concepts == []:
|
|
506
|
+
if self.concept_pairs or self.concepts == []:
|
|
486
507
|
return self
|
|
487
508
|
|
|
488
|
-
#
|
|
509
|
+
# reduce concept list to just the mutual keys
|
|
489
510
|
final_concepts = []
|
|
490
|
-
assert self.left_datasource and self.right_datasource
|
|
491
|
-
|
|
492
511
|
for concept in self.concepts or []:
|
|
493
512
|
include = True
|
|
494
513
|
for ds in [self.left_datasource, self.right_datasource]:
|
|
495
514
|
synonyms = []
|
|
515
|
+
if not ds:
|
|
516
|
+
continue
|
|
496
517
|
for c in ds.output_concepts:
|
|
497
518
|
synonyms += list(c.pseudonyms)
|
|
498
519
|
if (
|
|
499
|
-
concept.address not in
|
|
520
|
+
concept.address not in ds.output_concepts
|
|
500
521
|
and concept.address not in synonyms
|
|
501
522
|
):
|
|
502
|
-
raise
|
|
523
|
+
raise InvalidSyntaxException(
|
|
503
524
|
f"Invalid join, missing {concept} on {ds.name}, have"
|
|
504
525
|
f" {[c.address for c in ds.output_concepts]}"
|
|
505
526
|
)
|
|
@@ -507,32 +528,10 @@ class BaseJoin(BaseModel):
|
|
|
507
528
|
final_concepts.append(concept)
|
|
508
529
|
|
|
509
530
|
if not final_concepts and self.concepts:
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if all(
|
|
515
|
-
[
|
|
516
|
-
c.granularity == Granularity.SINGLE_ROW
|
|
517
|
-
for c in ds.output_concepts
|
|
518
|
-
]
|
|
519
|
-
):
|
|
520
|
-
self.concepts = []
|
|
521
|
-
return self
|
|
522
|
-
# if everything is at abstract grain, we can skip joins
|
|
523
|
-
if all([c.grain.abstract for c in ds.output_concepts]):
|
|
524
|
-
self.concepts = []
|
|
525
|
-
return self
|
|
526
|
-
|
|
527
|
-
left_keys = [c.address for c in self.left_datasource.output_concepts]
|
|
528
|
-
right_keys = [c.address for c in self.right_datasource.output_concepts]
|
|
529
|
-
match_concepts = [c.address for c in self.concepts]
|
|
530
|
-
raise SyntaxError(
|
|
531
|
-
"No mutual join keys found between"
|
|
532
|
-
f" {self.left_datasource.identifier} and"
|
|
533
|
-
f" {self.right_datasource.identifier}, left_keys {left_keys},"
|
|
534
|
-
f" right_keys {right_keys},"
|
|
535
|
-
f" provided join concepts {match_concepts}"
|
|
531
|
+
raise_helpful_join_validation_error(
|
|
532
|
+
self.concepts,
|
|
533
|
+
self.left_datasource,
|
|
534
|
+
self.right_datasource,
|
|
536
535
|
)
|
|
537
536
|
|
|
538
537
|
self.concepts = final_concepts
|
|
@@ -1087,7 +1086,7 @@ class UnionCTE(BaseModel):
|
|
|
1087
1086
|
class Join(BaseModel):
|
|
1088
1087
|
right_cte: CTE | UnionCTE
|
|
1089
1088
|
jointype: JoinType
|
|
1090
|
-
left_cte: CTE | None = None
|
|
1089
|
+
left_cte: CTE | UnionCTE | None = None
|
|
1091
1090
|
joinkey_pairs: List[CTEConceptPair] | None = None
|
|
1092
1091
|
inlined_ctes: set[str] = Field(default_factory=set)
|
|
1093
1092
|
quote: str | None = None
|
|
@@ -1096,7 +1095,7 @@ class Join(BaseModel):
|
|
|
1096
1095
|
def inline_cte(self, cte: CTE):
|
|
1097
1096
|
self.inlined_ctes.add(cte.name)
|
|
1098
1097
|
|
|
1099
|
-
def get_name(self, cte: CTE):
|
|
1098
|
+
def get_name(self, cte: CTE | UnionCTE) -> str:
|
|
1100
1099
|
if cte.identifier in self.inlined_ctes:
|
|
1101
1100
|
return cte.source.datasources[0].safe_identifier
|
|
1102
1101
|
return cte.safe_identifier
|
|
@@ -291,7 +291,7 @@ def evaluate_loop_conditions(
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def check_for_early_exit(
|
|
294
|
-
complete, partial, context: LoopContext, priority_concept: BuildConcept
|
|
294
|
+
complete, partial, missing, context: LoopContext, priority_concept: BuildConcept
|
|
295
295
|
) -> bool:
|
|
296
296
|
if complete == ValidationResult.INCOMPLETE_CONDITION:
|
|
297
297
|
cond_dict = {str(node): node.preexisting_conditions for node in context.stack}
|
|
@@ -321,7 +321,7 @@ def check_for_early_exit(
|
|
|
321
321
|
)
|
|
322
322
|
else:
|
|
323
323
|
logger.info(
|
|
324
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Not complete, continuing search"
|
|
324
|
+
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Not complete (missing {missing}), continuing search"
|
|
325
325
|
)
|
|
326
326
|
# if we have attempted on root node, we've tried them all.
|
|
327
327
|
# inject in another search with filter concepts
|
|
@@ -412,7 +412,7 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
|
|
|
412
412
|
elif context.conditions:
|
|
413
413
|
output.preexisting_conditions = context.conditions.conditional
|
|
414
414
|
logger.info(
|
|
415
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node output {[x.address for x in output.usable_outputs]} partial {[c.address for c in output.partial_concepts]} with {context.conditions}"
|
|
415
|
+
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Graph is connected, returning {type(output)} node output {[x.address for x in output.usable_outputs]} partial {[c.address for c in output.partial_concepts or []]} with {context.conditions}"
|
|
416
416
|
)
|
|
417
417
|
if condition_required and context.conditions and non_virtual_different:
|
|
418
418
|
logger.info(
|
|
@@ -532,7 +532,9 @@ def _search_concepts(
|
|
|
532
532
|
)
|
|
533
533
|
# assign
|
|
534
534
|
context.found = found_c
|
|
535
|
-
early_exit = check_for_early_exit(
|
|
535
|
+
early_exit = check_for_early_exit(
|
|
536
|
+
complete, partial, missing_c, context, priority_concept
|
|
537
|
+
)
|
|
536
538
|
if early_exit:
|
|
537
539
|
break
|
|
538
540
|
|
{pytrilogy-0.0.3.70 → pytrilogy-0.0.3.72}/trilogy/core/processing/node_generators/node_merge_node.py
RENAMED
|
@@ -253,7 +253,9 @@ def resolve_weak_components(
|
|
|
253
253
|
break_flag = False
|
|
254
254
|
found = []
|
|
255
255
|
search_graph = environment_graph.copy()
|
|
256
|
-
prune_sources_for_conditions(
|
|
256
|
+
prune_sources_for_conditions(
|
|
257
|
+
search_graph, accept_partial, conditions=search_conditions
|
|
258
|
+
)
|
|
257
259
|
reduced_concept_sets: list[set[str]] = []
|
|
258
260
|
|
|
259
261
|
# loop through, removing new nodes we find
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from collections import defaultdict
|
|
2
3
|
from datetime import date, datetime, timedelta
|
|
3
4
|
from typing import List, Tuple, TypeVar
|
|
@@ -14,7 +15,7 @@ from trilogy.core.models.build import (
|
|
|
14
15
|
from trilogy.core.models.core import DataType
|
|
15
16
|
|
|
16
17
|
# Define a generic type that ensures start and end are the same type
|
|
17
|
-
T = TypeVar("T", int, date, datetime)
|
|
18
|
+
T = TypeVar("T", int, float, date, datetime)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def reduce_expression(
|
|
@@ -37,21 +38,26 @@ def reduce_expression(
|
|
|
37
38
|
elif var.datatype == DataType.DATETIME:
|
|
38
39
|
lower_check = datetime.min # type: ignore
|
|
39
40
|
upper_check = datetime.max # type: ignore
|
|
41
|
+
elif var.datatype == DataType.BOOL:
|
|
42
|
+
lower_check = False # type: ignore
|
|
43
|
+
upper_check = True # type: ignore
|
|
44
|
+
elif var.datatype == DataType.FLOAT:
|
|
45
|
+
lower_check = float("-inf") # type: ignore
|
|
46
|
+
upper_check = float("inf") # type: ignore
|
|
40
47
|
else:
|
|
41
|
-
|
|
48
|
+
return False
|
|
42
49
|
|
|
43
50
|
ranges: list[Tuple[T, T]] = []
|
|
44
51
|
for op, value in group_tuple:
|
|
45
|
-
increment: int | timedelta
|
|
52
|
+
increment: int | timedelta | float
|
|
46
53
|
if isinstance(value, date):
|
|
47
54
|
increment = timedelta(days=1)
|
|
48
55
|
elif isinstance(value, datetime):
|
|
49
56
|
increment = timedelta(seconds=1)
|
|
50
57
|
elif isinstance(value, int):
|
|
51
58
|
increment = 1
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# increment = Decimal(0.0000000001)
|
|
59
|
+
elif isinstance(value, float):
|
|
60
|
+
increment = sys.float_info.epsilon
|
|
55
61
|
|
|
56
62
|
if op == ">":
|
|
57
63
|
ranges.append(
|
|
@@ -88,24 +94,43 @@ def reduce_expression(
|
|
|
88
94
|
value,
|
|
89
95
|
)
|
|
90
96
|
)
|
|
97
|
+
elif op == ComparisonOperator.IS:
|
|
98
|
+
ranges.append(
|
|
99
|
+
(
|
|
100
|
+
value,
|
|
101
|
+
value,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
elif op == ComparisonOperator.NE:
|
|
105
|
+
pass
|
|
91
106
|
else:
|
|
92
|
-
|
|
107
|
+
return False
|
|
93
108
|
return is_fully_covered(lower_check, upper_check, ranges, increment)
|
|
94
109
|
|
|
95
110
|
|
|
111
|
+
TARGET_TYPES = (
|
|
112
|
+
int,
|
|
113
|
+
date,
|
|
114
|
+
float,
|
|
115
|
+
datetime,
|
|
116
|
+
bool,
|
|
117
|
+
)
|
|
118
|
+
REDUCABLE_TYPES = (int, float, date, bool, datetime, BuildFunction)
|
|
119
|
+
|
|
120
|
+
|
|
96
121
|
def simplify_conditions(
|
|
97
122
|
conditions: list[BuildComparison | BuildConditional | BuildParenthetical],
|
|
98
123
|
) -> bool:
|
|
99
124
|
# Group conditions by variable
|
|
100
125
|
grouped: dict[
|
|
101
|
-
BuildConcept, list[tuple[ComparisonOperator, datetime | int | date]]
|
|
126
|
+
BuildConcept, list[tuple[ComparisonOperator, datetime | int | date | float]]
|
|
102
127
|
] = defaultdict(list)
|
|
103
128
|
for condition in conditions:
|
|
104
129
|
if not isinstance(condition, BuildComparison):
|
|
105
130
|
return False
|
|
106
|
-
if not isinstance(
|
|
107
|
-
condition.
|
|
108
|
-
)
|
|
131
|
+
if not isinstance(condition.left, REDUCABLE_TYPES) and not isinstance(
|
|
132
|
+
condition.right, REDUCABLE_TYPES
|
|
133
|
+
):
|
|
109
134
|
return False
|
|
110
135
|
if not isinstance(condition.left, BuildConcept) and not isinstance(
|
|
111
136
|
condition.right, BuildConcept
|
|
@@ -113,15 +138,20 @@ def simplify_conditions(
|
|
|
113
138
|
return False
|
|
114
139
|
vars = [condition.left, condition.right]
|
|
115
140
|
concept = [x for x in vars if isinstance(x, BuildConcept)][0]
|
|
116
|
-
|
|
117
|
-
if isinstance(
|
|
118
|
-
if not
|
|
141
|
+
raw_comparison = [x for x in vars if not isinstance(x, BuildConcept)][0]
|
|
142
|
+
if isinstance(raw_comparison, BuildFunction):
|
|
143
|
+
if not raw_comparison.operator == FunctionType.CONSTANT:
|
|
119
144
|
return False
|
|
120
|
-
first_arg =
|
|
121
|
-
if not isinstance(first_arg,
|
|
145
|
+
first_arg = raw_comparison.arguments[0]
|
|
146
|
+
if not isinstance(first_arg, TARGET_TYPES):
|
|
122
147
|
return False
|
|
123
148
|
comparison = first_arg
|
|
124
|
-
|
|
149
|
+
else:
|
|
150
|
+
if not isinstance(raw_comparison, TARGET_TYPES):
|
|
151
|
+
return False
|
|
152
|
+
comparison = raw_comparison
|
|
153
|
+
|
|
154
|
+
if not isinstance(comparison, REDUCABLE_TYPES):
|
|
125
155
|
return False
|
|
126
156
|
|
|
127
157
|
var = concept
|
|
@@ -136,11 +166,25 @@ def simplify_conditions(
|
|
|
136
166
|
return True if all(isinstance(s, bool) and s for s in simplified) else False
|
|
137
167
|
|
|
138
168
|
|
|
169
|
+
def boolean_fully_covered(
|
|
170
|
+
start: bool,
|
|
171
|
+
end: bool,
|
|
172
|
+
ranges: List[Tuple[bool, bool]],
|
|
173
|
+
):
|
|
174
|
+
all = []
|
|
175
|
+
for r_start, r_end in ranges:
|
|
176
|
+
if r_start is True and r_end is True:
|
|
177
|
+
all.append(True)
|
|
178
|
+
elif r_start is False and r_end is False:
|
|
179
|
+
all.append(False)
|
|
180
|
+
return set(all) == {False, True}
|
|
181
|
+
|
|
182
|
+
|
|
139
183
|
def is_fully_covered(
|
|
140
184
|
start: T,
|
|
141
185
|
end: T,
|
|
142
186
|
ranges: List[Tuple[T, T]],
|
|
143
|
-
increment: int | timedelta,
|
|
187
|
+
increment: int | timedelta | float,
|
|
144
188
|
):
|
|
145
189
|
"""
|
|
146
190
|
Check if the list of range pairs fully covers the set [start, end].
|
|
@@ -153,6 +197,11 @@ def is_fully_covered(
|
|
|
153
197
|
Returns:
|
|
154
198
|
- bool: True if the ranges fully cover [start, end], False otherwise.
|
|
155
199
|
"""
|
|
200
|
+
if isinstance(start, bool) and isinstance(end, bool):
|
|
201
|
+
# convert each element of each tuple to a boolean
|
|
202
|
+
bool_ranges = [(bool(r_start), bool(r_end)) for r_start, r_end in ranges]
|
|
203
|
+
|
|
204
|
+
return boolean_fully_covered(start, end, bool_ranges)
|
|
156
205
|
# Sort ranges by their start values (and by end values for ties)
|
|
157
206
|
ranges.sort()
|
|
158
207
|
|
|
@@ -173,14 +222,14 @@ def get_union_sources(
|
|
|
173
222
|
datasources: list[BuildDatasource], concepts: list[BuildConcept]
|
|
174
223
|
) -> List[list[BuildDatasource]]:
|
|
175
224
|
candidates: list[BuildDatasource] = []
|
|
225
|
+
|
|
176
226
|
for x in datasources:
|
|
177
|
-
if
|
|
227
|
+
if any([c.address in x.output_concepts for c in concepts]):
|
|
178
228
|
if (
|
|
179
229
|
any([c.address in x.partial_concepts for c in concepts])
|
|
180
230
|
and x.non_partial_for
|
|
181
231
|
):
|
|
182
232
|
candidates.append(x)
|
|
183
|
-
|
|
184
233
|
assocs: dict[str, list[BuildDatasource]] = defaultdict(list[BuildDatasource])
|
|
185
234
|
for x in candidates:
|
|
186
235
|
if not x.non_partial_for:
|