pytrilogy 0.0.2.19__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.19
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=3gRtKqbvnX1RJDJWia2dlhjAU87WHpfzoZZM3KSaFaw,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=nSCOXedYRFgYdZa45dputERCvgPRQpRvCyiGglyeM0g,150985
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
@@ -70,14 +70,14 @@ trilogy/parsing/common.py,sha256=-4LM71ocidA8DI2RngqFEOmhzBrIt8VdBTO4x2BpD8E,950
70
70
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
71
71
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
72
72
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
73
- trilogy/parsing/parse_engine.py,sha256=r0B7tXAIfTMJ42ZJwP70ulQBrOoM1AAiXFbCQ2rOpQs,64720
73
+ trilogy/parsing/parse_engine.py,sha256=_orHmZTdExJYkuNRmAaazdXpZcvtPMLKIBI9vBAgq6w,64871
74
74
  trilogy/parsing/render.py,sha256=8yxerPAi4AhlhPBlAfbYbOM3F9rz6HzpWVEWPtK2VEg,12321
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.19.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
79
- pytrilogy-0.0.2.19.dist-info/METADATA,sha256=BgEvyZ13aahBi8bULS0lT9Rx5ixjpZ99ZbHeC8mg5Mw,8132
80
- pytrilogy-0.0.2.19.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
81
- pytrilogy-0.0.2.19.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
82
- pytrilogy-0.0.2.19.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
83
- pytrilogy-0.0.2.19.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.19"
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,
@@ -559,19 +560,16 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
559
560
  grain = ",".join([str(c.address) for c in self.grain.components])
560
561
  return f"{self.namespace}.{self.name}<{grain}>"
561
562
 
562
- @property
563
+ @cached_property
563
564
  def address(self) -> str:
564
- if not self._address_cache:
565
- self._address_cache = f"{self.namespace}.{self.name}"
566
- return self._address_cache
567
-
568
- @address.setter
569
- def address(self, address: str) -> None:
570
- self._address_cache = address
565
+ return f"{self.namespace}.{self.name}"
571
566
 
572
567
  def set_name(self, name: str):
573
568
  self.name = name
574
- self.address = f"{self.namespace}.{self.name}"
569
+ try:
570
+ del self.address
571
+ except AttributeError:
572
+ pass
575
573
 
576
574
  @property
577
575
  def output(self) -> "Concept":
@@ -915,10 +913,10 @@ class Grain(Mergeable, BaseModel):
915
913
  )
916
914
 
917
915
  @cached_property
918
- def set(self):
916
+ def set(self) -> set[str]:
919
917
  base = []
920
918
  for x in self.components_copy:
921
- if x.derivation == PurposeLineage.ROWSET:
919
+ if isinstance(x.lineage, RowsetItem):
922
920
  base.append(x.lineage.content.address)
923
921
  else:
924
922
  base.append(x.address)
@@ -3134,6 +3132,12 @@ class EnvironmentConceptDict(dict):
3134
3132
  def values(self) -> ValuesView[Concept]: # type: ignore
3135
3133
  return super().values()
3136
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
+
3137
3141
  def __getitem__(
3138
3142
  self, key, line_no: int | None = None
3139
3143
  ) -> Concept | UndefinedConcept:
@@ -3260,34 +3264,62 @@ class Environment(BaseModel):
3260
3264
  for datasource in self.datasources.values():
3261
3265
  for concept in datasource.output_concepts:
3262
3266
  concrete_addresses.add(concept.address)
3263
- current_mat = [x.address for x in self.materialized_concepts]
3264
- self.materialized_concepts = [
3265
- c for c in self.concepts.values() if c.address in concrete_addresses
3266
- ]
3267
- # include aliased concepts
3268
- self.materialized_concepts += [
3269
- c
3270
- for c in self.alias_origin_lookup.values()
3271
- if c.address in concrete_addresses
3272
- ]
3273
- new = [
3274
- x.address
3275
- for x in self.materialized_concepts
3276
- if x.address not in current_mat
3277
- ]
3278
- if new:
3279
- logger.debug(f"Environment added new materialized concepts {new}")
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
+ )
3280
3276
 
3281
- 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
3282
3279
  existing: Concept = self.concepts.get(lookup) # type: ignore
3283
3280
  if not existing:
3284
3281
  return
3285
- 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()
3286
3316
  return
3287
3317
  elif existing.metadata:
3318
+ if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
3319
+ return handle_persist()
3288
3320
  # if the existing concept is auto derived, we can overwrite it
3289
3321
  if existing.metadata.concept_source == ConceptSource.AUTO_DERIVED:
3290
- return
3322
+ return None
3291
3323
  elif meta and existing.metadata:
3292
3324
  raise ValueError(
3293
3325
  f"Assignment to concept '{lookup}' on line {meta.line} is a duplicate"
@@ -3400,39 +3432,58 @@ class Environment(BaseModel):
3400
3432
  meta: Meta | None = None,
3401
3433
  force: bool = False,
3402
3434
  add_derived: bool = True,
3435
+ _ignore_cache: bool = False,
3403
3436
  ):
3404
3437
  if not force:
3405
- self.validate_concept(concept.address, meta=meta)
3438
+ existing = self.validate_concept(concept, meta=meta)
3439
+ if existing:
3440
+ concept = existing
3406
3441
  if concept.namespace == DEFAULT_NAMESPACE:
3407
3442
  self.concepts[concept.name] = concept
3408
- else:
3409
- self.concepts[concept.address] = concept
3443
+ self.concepts[concept.address] = concept
3410
3444
  from trilogy.core.environment_helpers import generate_related_concepts
3411
3445
 
3412
3446
  generate_related_concepts(concept, self, meta=meta, add_derived=add_derived)
3413
- self.gen_concept_list_caches()
3447
+ if not _ignore_cache:
3448
+ self.gen_concept_list_caches()
3414
3449
  return concept
3415
3450
 
3416
3451
  def add_datasource(
3417
3452
  self,
3418
3453
  datasource: Datasource,
3419
3454
  meta: Meta | None = None,
3455
+ _ignore_cache: bool = False,
3420
3456
  ):
3421
-
3422
3457
  self.datasources[datasource.env_label] = datasource
3423
- for column in datasource.columns:
3424
- current_concept = column.concept
3458
+ for current_concept in datasource.output_concepts:
3425
3459
  current_derivation = current_concept.derivation
3460
+ # TODO: refine this section;
3461
+ # too hacky for maintainability
3426
3462
  if current_derivation not in (PurposeLineage.ROOT, PurposeLineage.CONSTANT):
3427
- new_concept = current_concept.model_copy(deep=True)
3428
- new_concept.set_name("_pre_persist_" + current_concept.name)
3429
- # remove the associated lineage
3430
- current_concept.lineage = None
3431
- self.add_concept(new_concept, meta=meta, force=True)
3432
- self.add_concept(current_concept, meta=meta, force=True)
3433
- self.merge_concept(new_concept, current_concept, [])
3434
-
3435
- self.gen_concept_list_caches()
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, [])
3485
+ if not _ignore_cache:
3486
+ self.gen_concept_list_caches()
3436
3487
  return datasource
3437
3488
 
3438
3489
  def delete_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
 
@@ -814,14 +814,19 @@ class ParseToObjects(Transformer):
814
814
  raise ImportError(f"Unable to import file {target}, parsing error: {e}")
815
815
 
816
816
  for _, concept in nparser.environment.concepts.items():
817
- self.environment.add_concept(concept.with_namespace(alias))
817
+ self.environment.add_concept(
818
+ concept.with_namespace(alias), _ignore_cache=True
819
+ )
818
820
 
819
821
  for _, datasource in nparser.environment.datasources.items():
820
- self.environment.add_datasource(datasource.with_namespace(alias))
822
+ self.environment.add_datasource(
823
+ datasource.with_namespace(alias), _ignore_cache=True
824
+ )
821
825
  imps = ImportStatement(
822
826
  alias=alias, path=Path(args[0]), environment=nparser.environment
823
827
  )
824
828
  self.environment.imports[alias] = imps
829
+ self.environment.gen_concept_list_caches()
825
830
  return imps
826
831
 
827
832
  @v_args(meta=True)