pytrilogy 0.0.2.20__py3-none-any.whl → 0.0.2.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.20
3
+ Version: 0.0.2.21
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,4 +1,4 @@
1
- trilogy/__init__.py,sha256=jkM9h2qEkIh-R8xZca7g_Qzl5cC8w48aOmkcJUjjTAs,291
1
+ trilogy/__init__.py,sha256=OCXbyl49qGgFMeTRpc5o4WNKO8b9oOs7BpkoN7kt4sg,291
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  trilogy/constants.py,sha256=pZkOneh_65f9Ua6NICu1bHAFAbmQxmiXRXS7tsmCWbQ,1235
4
4
  trilogy/engine.py,sha256=R5ubIxYyrxRExz07aZCUfrTsoXCHQ8DKFTDsobXdWdA,1102
@@ -7,8 +7,8 @@ trilogy/parser.py,sha256=UtuqSiGiCjpMAYgo1bvNq-b7NSzCA5hzbUW31RXaMII,281
7
7
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  trilogy/utility.py,sha256=zM__8r29EsyDW7K9VOHz8yvZC2bXFzh7xKy3cL7GKsk,707
9
9
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- trilogy/core/constants.py,sha256=LL8NLvxb3HRnAjvofyLRXqQJijLcYiXAQYQzGarVD-g,128
11
- trilogy/core/enums.py,sha256=A9VC0lbP5eo9sndm2TzA-nNJRRmvbjE918ZiEXtcQ_c,6043
10
+ trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
11
+ trilogy/core/enums.py,sha256=IHi-Z656detnp2MKMeCphsnVL7-uaogehulJT3JA64I,6087
12
12
  trilogy/core/env_processor.py,sha256=l7TAB0LalxjTYJdTlcmFIkLXuyxa9lrenWLeZfa9qw0,2276
13
13
  trilogy/core/environment_helpers.py,sha256=1miP4is4FEoci01KSAy2VZVYmlmT5TOCOALBekd2muQ,7211
14
14
  trilogy/core/ergonomics.py,sha256=w3gwXdgrxNHCuaRdyKg73t6F36tj-wIjQf47WZkHmJk,1465
@@ -16,9 +16,9 @@ trilogy/core/exceptions.py,sha256=NvV_4qLOgKXbpotgRf7c8BANDEvHxlqRPaA53IThQ2o,56
16
16
  trilogy/core/functions.py,sha256=ShFTStIKbgI-3EZIU0xTumI78AC5QlvARwnBM53P2O0,10677
17
17
  trilogy/core/graph_models.py,sha256=oJUMSpmYhqXlavckHLpR07GJxuQ8dZ1VbB1fB0KaS8c,2036
18
18
  trilogy/core/internal.py,sha256=jNGFHKENnbMiMCtAgsnLZYVSENDK4b5ALecXFZpTDzQ,1075
19
- trilogy/core/models.py,sha256=O1_HxOQ0muMbjWVcOH1mmEFG3IM7taaPgfgCnZv21W8,150676
19
+ trilogy/core/models.py,sha256=2YoDJz0cK5EljGtWCt1uFtcV56VWS9v8RfHw1nvaFrc,153416
20
20
  trilogy/core/optimization.py,sha256=od_60A9F8J8Nj24MHgrxl4vwRwmBFH13TMdoMQvgVKs,7717
21
- trilogy/core/query_processor.py,sha256=kXuBsIaRHu1s7zB_rAnT_gRe4-VgRSrPE1TnVJXFLtc,16447
21
+ trilogy/core/query_processor.py,sha256=D4-FKc-wP6c98-35DPcBE7-Gp6vbyj9DTZrubgtqACA,16561
22
22
  trilogy/core/optimizations/__init__.py,sha256=bWQecbeiwiDx9LJnLsa7dkWxdbl2wcnkcTN69JyP8iI,356
23
23
  trilogy/core/optimizations/base_optimization.py,sha256=tWWT-xnTbnEU-mNi_isMNbywm8B9WTRsNFwGpeh3rqE,468
24
24
  trilogy/core/optimizations/inline_constant.py,sha256=kHNyc2UoaPVdYfVAPAFwnWuk4sJ_IF5faRtVcDOrBtw,1110
@@ -38,7 +38,7 @@ trilogy/core/processing/node_generators/multiselect_node.py,sha256=_KO9lqzHQoy4V
38
38
  trilogy/core/processing/node_generators/node_merge_node.py,sha256=4aoSkynWYcKAxeN4fU5jnCdxausa5rNgFokoVhPXI80,13511
39
39
  trilogy/core/processing/node_generators/rowset_node.py,sha256=gU_ybfYXO9tZqHjUSABIioVpb8AWtITpegj3IGSf2GI,4587
40
40
  trilogy/core/processing/node_generators/select_merge_node.py,sha256=MKjlXqFBSin6cTnS6n5lEcNBJsMvSefDIXOwYNVbM0s,10371
41
- trilogy/core/processing/node_generators/select_node.py,sha256=vUg3gXHGvagdbniIAE7DdqJcQ0V1VAfHtTrw3edYPso,1734
41
+ trilogy/core/processing/node_generators/select_node.py,sha256=nwXHQF6C-aQUIelx9dyxN2pK3muL-4-6RIqnqQqNwtw,1808
42
42
  trilogy/core/processing/node_generators/unnest_node.py,sha256=cZ26CN338CBnd6asML1OBUtNcDzmNlFpY0Vnade4yrc,2256
43
43
  trilogy/core/processing/node_generators/window_node.py,sha256=jy3FF8uN0VA7yyrBeR40B9CAqR_5qBP4PiS6Gr-f-7w,2590
44
44
  trilogy/core/processing/nodes/__init__.py,sha256=qS5EJDRwwIrCEfS7ibCA2ESE0RPzsAIii1UWd_wNsHA,4760
@@ -75,9 +75,9 @@ trilogy/parsing/render.py,sha256=8yxerPAi4AhlhPBlAfbYbOM3F9rz6HzpWVEWPtK2VEg,123
75
75
  trilogy/parsing/trilogy.lark,sha256=0JAvQBACFNL-X61I0tB_0QPZgsguZgerfHBv903oKh0,11623
76
76
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  trilogy/scripts/trilogy.py,sha256=PHxvv6f2ODv0esyyhWxlARgra8dVhqQhYl0lTrSyVNo,3729
78
- pytrilogy-0.0.2.20.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
- pytrilogy-0.0.2.20.dist-info/METADATA,sha256=pmZ2uTW1EBclZPel8B698DU3sdTmutM5vnu25tNTHfM,8132
80
- pytrilogy-0.0.2.20.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
81
- pytrilogy-0.0.2.20.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
- pytrilogy-0.0.2.20.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
- pytrilogy-0.0.2.20.dist-info/RECORD,,
78
+ pytrilogy-0.0.2.21.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
+ pytrilogy-0.0.2.21.dist-info/METADATA,sha256=zUKc5pQLdUDjCdk3crvgw6wTA_caPxs0qbHGdm_kD8E,8132
80
+ pytrilogy-0.0.2.21.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
81
+ pytrilogy-0.0.2.21.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
+ pytrilogy-0.0.2.21.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
+ pytrilogy-0.0.2.21.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.20"
7
+ __version__ = "0.0.2.21"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/constants.py CHANGED
@@ -1,3 +1,4 @@
1
1
  CONSTANT_DATASET: str = "preql_internal_constant_dataset"
2
2
  ALL_ROWS_CONCEPT = "all_rows"
3
3
  INTERNAL_NAMESPACE = "__preql_internal"
4
+ PERSISTED_CONCEPT_PREFIX = "__pre_persist"
trilogy/core/enums.py CHANGED
@@ -12,6 +12,7 @@ class UnnestMode(Enum):
12
12
 
13
13
  class ConceptSource(Enum):
14
14
  MANUAL = "manual"
15
+ PERSIST_STATEMENT = "persist_statement"
15
16
  AUTO_DERIVED = "auto_derived"
16
17
 
17
18
 
trilogy/core/models.py CHANGED
@@ -44,6 +44,7 @@ from trilogy.core.constants import (
44
44
  ALL_ROWS_CONCEPT,
45
45
  INTERNAL_NAMESPACE,
46
46
  CONSTANT_DATASET,
47
+ PERSISTED_CONCEPT_PREFIX,
47
48
  )
48
49
  from trilogy.core.enums import (
49
50
  InfiniteFunctionArgs,
@@ -912,10 +913,10 @@ class Grain(Mergeable, BaseModel):
912
913
  )
913
914
 
914
915
  @cached_property
915
- def set(self):
916
+ def set(self) -> set[str]:
916
917
  base = []
917
918
  for x in self.components_copy:
918
- if x.derivation == PurposeLineage.ROWSET:
919
+ if isinstance(x.lineage, RowsetItem):
919
920
  base.append(x.lineage.content.address)
920
921
  else:
921
922
  base.append(x.address)
@@ -3131,6 +3132,12 @@ class EnvironmentConceptDict(dict):
3131
3132
  def values(self) -> ValuesView[Concept]: # type: ignore
3132
3133
  return super().values()
3133
3134
 
3135
+ def get(self, key: str, default: Concept | None = None) -> Concept | None: # type: ignore
3136
+ try:
3137
+ return self.__getitem__(key)
3138
+ except UndefinedConceptException:
3139
+ return default
3140
+
3134
3141
  def __getitem__(
3135
3142
  self, key, line_no: int | None = None
3136
3143
  ) -> Concept | UndefinedConcept:
@@ -3257,24 +3264,62 @@ class Environment(BaseModel):
3257
3264
  for datasource in self.datasources.values():
3258
3265
  for concept in datasource.output_concepts:
3259
3266
  concrete_addresses.add(concept.address)
3260
- self.materialized_concepts = [
3261
- c for c in self.concepts.values() if c.address in concrete_addresses
3262
- ] + [
3263
- c
3264
- for c in self.alias_origin_lookup.values()
3265
- if c.address in concrete_addresses
3266
- ]
3267
+ self.materialized_concepts = unique(
3268
+ [c for c in self.concepts.values() if c.address in concrete_addresses]
3269
+ + [
3270
+ c
3271
+ for c in self.alias_origin_lookup.values()
3272
+ if c.address in concrete_addresses
3273
+ ],
3274
+ "address",
3275
+ )
3267
3276
 
3268
- def validate_concept(self, lookup: str, meta: Meta | None = None):
3277
+ def validate_concept(self, new_concept: Concept, meta: Meta | None = None):
3278
+ lookup = new_concept.address
3269
3279
  existing: Concept = self.concepts.get(lookup) # type: ignore
3270
3280
  if not existing:
3271
3281
  return
3272
- elif existing and self.environment_config.allow_duplicate_declaration:
3282
+
3283
+ def handle_persist():
3284
+ deriv_lookup = (
3285
+ f"{existing.namespace}.{PERSISTED_CONCEPT_PREFIX}_{existing.name}"
3286
+ )
3287
+
3288
+ alt_source = self.alias_origin_lookup.get(deriv_lookup)
3289
+ if not alt_source:
3290
+ return None
3291
+ # if the new concept binding has no lineage
3292
+ # nothing to cause us to think a persist binding
3293
+ # needs to be invalidated
3294
+ if not new_concept.lineage:
3295
+ return existing
3296
+ if str(alt_source.lineage) == str(new_concept.lineage):
3297
+ logger.info(
3298
+ f"Persisted concept {existing.address} matched redeclaration, keeping current persistence binding."
3299
+ )
3300
+ return existing
3301
+ logger.warning(
3302
+ f"Persisted concept {existing.address} lineage {str(alt_source.lineage)} did not match redeclaration {str(new_concept.lineage)}, overwriting and invalidating persist binding."
3303
+ )
3304
+ for k, datasource in self.datasources.items():
3305
+ if existing.address in datasource.output_concepts:
3306
+ datasource.columns = [
3307
+ x
3308
+ for x in datasource.columns
3309
+ if x.concept.address != existing.address
3310
+ ]
3311
+ return None
3312
+
3313
+ if existing and self.environment_config.allow_duplicate_declaration:
3314
+ if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
3315
+ return handle_persist()
3273
3316
  return
3274
3317
  elif existing.metadata:
3318
+ if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
3319
+ return handle_persist()
3275
3320
  # if the existing concept is auto derived, we can overwrite it
3276
3321
  if existing.metadata.concept_source == ConceptSource.AUTO_DERIVED:
3277
- return
3322
+ return None
3278
3323
  elif meta and existing.metadata:
3279
3324
  raise ValueError(
3280
3325
  f"Assignment to concept '{lookup}' on line {meta.line} is a duplicate"
@@ -3390,11 +3435,12 @@ class Environment(BaseModel):
3390
3435
  _ignore_cache: bool = False,
3391
3436
  ):
3392
3437
  if not force:
3393
- self.validate_concept(concept.address, meta=meta)
3438
+ existing = self.validate_concept(concept, meta=meta)
3439
+ if existing:
3440
+ concept = existing
3394
3441
  if concept.namespace == DEFAULT_NAMESPACE:
3395
3442
  self.concepts[concept.name] = concept
3396
- else:
3397
- self.concepts[concept.address] = concept
3443
+ self.concepts[concept.address] = concept
3398
3444
  from trilogy.core.environment_helpers import generate_related_concepts
3399
3445
 
3400
3446
  generate_related_concepts(concept, self, meta=meta, add_derived=add_derived)
@@ -3408,21 +3454,34 @@ class Environment(BaseModel):
3408
3454
  meta: Meta | None = None,
3409
3455
  _ignore_cache: bool = False,
3410
3456
  ):
3411
-
3412
3457
  self.datasources[datasource.env_label] = datasource
3413
- for column in datasource.columns:
3414
- current_concept = column.concept
3458
+ for current_concept in datasource.output_concepts:
3415
3459
  current_derivation = current_concept.derivation
3460
+ # TODO: refine this section;
3461
+ # too hacky for maintainability
3416
3462
  if current_derivation not in (PurposeLineage.ROOT, PurposeLineage.CONSTANT):
3417
- new_concept = current_concept.model_copy(deep=True)
3418
- new_concept.set_name("_pre_persist_" + current_concept.name)
3419
- # remove the associated lineage
3420
- current_concept.lineage = None
3421
- self.add_concept(new_concept, meta=meta, force=True, _ignore_cache=True)
3422
- self.add_concept(
3423
- current_concept, meta=meta, force=True, _ignore_cache=True
3424
- )
3425
- self.merge_concept(new_concept, current_concept, [])
3463
+ persisted = f"{PERSISTED_CONCEPT_PREFIX}_" + current_concept.name
3464
+ # override the current concept source to reflect that it's now coming from a datasource
3465
+ if (
3466
+ current_concept.metadata.concept_source
3467
+ != ConceptSource.PERSIST_STATEMENT
3468
+ ):
3469
+ new_concept = current_concept.model_copy(deep=True)
3470
+ new_concept.set_name(persisted)
3471
+ self.add_concept(
3472
+ new_concept, meta=meta, force=True, _ignore_cache=True
3473
+ )
3474
+ current_concept.metadata.concept_source = (
3475
+ ConceptSource.PERSIST_STATEMENT
3476
+ )
3477
+ # remove the associated lineage
3478
+ # to make this a root for discovery purposes
3479
+ # as it now "exists" in a table
3480
+ current_concept.lineage = None
3481
+ self.add_concept(
3482
+ current_concept, meta=meta, force=True, _ignore_cache=True
3483
+ )
3484
+ self.merge_concept(new_concept, current_concept, [])
3426
3485
  if not _ignore_cache:
3427
3486
  self.gen_concept_list_caches()
3428
3487
  return datasource
@@ -39,9 +39,10 @@ def gen_select_node(
39
39
  ]
40
40
  )
41
41
  if materialized_lcl != all_lcl:
42
+ missing = all_lcl.difference(materialized_lcl)
42
43
  logger.info(
43
44
  f"{padding(depth)}{LOGGER_PREFIX} Skipping select node generation for {concept.address}"
44
- f" as it + optional includes non-materialized concepts (looking for all {all_lcl}) "
45
+ f" as it + optional includes non-materialized concepts (looking for all {all_lcl}, missing {missing}) "
45
46
  )
46
47
  if fail_if_not_found:
47
48
  raise NoDatasourceException(f"No datasource exists for {concept}")
@@ -11,6 +11,7 @@ from trilogy.core.models import (
11
11
  Concept,
12
12
  Environment,
13
13
  PersistStatement,
14
+ ConceptDeclarationStatement,
14
15
  SelectStatement,
15
16
  MultiSelectStatement,
16
17
  CTE,
@@ -393,6 +394,8 @@ def process_auto(
393
394
  return process_persist(environment, statement, hooks)
394
395
  elif isinstance(statement, SelectStatement):
395
396
  return process_query(environment, statement, hooks)
397
+ elif isinstance(statement, ConceptDeclarationStatement):
398
+ return None
396
399
  raise ValueError(f"Do not know how to process {type(statement)}")
397
400
 
398
401