run-cache 2.2.0__tar.gz → 2.2.2__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.
Files changed (45) hide show
  1. {run_cache-2.2.0 → run_cache-2.2.2}/PKG-INFO +3 -3
  2. run_cache-2.2.2/src/dbt_run_cache/_version.py +1 -0
  3. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/base.py +84 -31
  4. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/snowflake.py +0 -27
  5. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/cli/explainer.py +5 -0
  6. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/run_cache.py +5 -8
  7. run_cache-2.2.0/src/dbt_run_cache/_version.py +0 -1
  8. {run_cache-2.2.0 → run_cache-2.2.2}/.gitignore +0 -0
  9. {run_cache-2.2.0 → run_cache-2.2.2}/pyproject.toml +0 -0
  10. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/__init__.py +0 -0
  11. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/_typing.py +0 -0
  12. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/__init__.py +0 -0
  13. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/bigquery.py +0 -0
  14. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/clock.py +0 -0
  15. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/common.py +0 -0
  16. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/databricks.py +0 -0
  17. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/adapters/postgres.py +0 -0
  18. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/__init__.py +0 -0
  19. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/grpc.py +0 -0
  20. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/oauth_clients.py +0 -0
  21. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/sso.py +0 -0
  22. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/sso_server.py +0 -0
  23. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/auth/utils.py +0 -0
  24. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/cli/__init__.py +0 -0
  25. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/cli/auth.py +0 -0
  26. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/cli/main.py +0 -0
  27. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/config.py +0 -0
  28. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/decision_logger.py +0 -0
  29. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/dev_cloner.py +0 -0
  30. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/dispatcher.py +0 -0
  31. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/errors.py +0 -0
  32. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/events.py +0 -0
  33. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/git.py +0 -0
  34. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/grpc/__init__.py +0 -0
  35. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/grpc/client.py +0 -0
  36. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/grpc/interceptors.py +0 -0
  37. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/plugin.py +0 -0
  38. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/profiles.py +0 -0
  39. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/relation.py +0 -0
  40. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/runner.py +0 -0
  41. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/selector.py +0 -0
  42. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/session.py +0 -0
  43. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/system_info.py +0 -0
  44. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/utils.py +0 -0
  45. {run_cache-2.2.0 → run_cache-2.2.2}/src/dbt_run_cache/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: run-cache
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: dbt plugin that skips redundant model executions by caching results from previous runs
5
5
  Project-URL: Homepage, https://runcache.com
6
6
  License: Copyright (c) 2026 Fivetran, Inc.
@@ -46,8 +46,8 @@ Requires-Dist: humanize
46
46
  Requires-Dist: protobuf>=4.0.0
47
47
  Requires-Dist: pyperclip
48
48
  Requires-Dist: pytimeparse2
49
- Requires-Dist: query-cache-common==1.3.0
50
- Requires-Dist: query-cache-protobuf==1.2.0
49
+ Requires-Dist: query-cache-common==1.4.0
50
+ Requires-Dist: query-cache-protobuf==1.3.0
51
51
  Requires-Dist: rich
52
52
  Requires-Dist: ruamel-yaml
53
53
  Requires-Dist: sqlglot>=27.6.1
@@ -0,0 +1 @@
1
+ __version__ = "2.2.2"
@@ -37,6 +37,7 @@ from dbt_run_cache.utils import find_tables
37
37
 
38
38
  if t.TYPE_CHECKING:
39
39
  from dbt.contracts.graph.nodes import ManifestNode
40
+ from dbt.contracts.graph.manifest import SourceDefinition
40
41
 
41
42
 
42
43
  class BaseAdapterExtension(abc.ABC):
@@ -146,11 +147,6 @@ class BaseAdapterExtension(abc.ABC):
146
147
  def rollback(self) -> None:
147
148
  self.adapter.connections.rollback_if_open()
148
149
 
149
- def table_(
150
- self, table: str, schema: t.Optional[str], catalog: t.Optional[str], quoted: bool = True
151
- ) -> exp.Table:
152
- return exp.table_(table, db=schema, catalog=catalog, quoted=quoted)
153
-
154
150
  def get_last_modified_epoch(
155
151
  self, tables: t.Iterable[str | exp.Table]
156
152
  ) -> dict[str, t.Optional[int]]:
@@ -470,7 +466,7 @@ class BaseAdapterExtension(abc.ABC):
470
466
  This is for when the plugin creates new database objects after dbt has populated its relation cache, to ensure those
471
467
  objects are reflected / available to the remainder of the dbt invocation.
472
468
  """
473
- relation = self._create_cachable_relation(node)
469
+ relation = self._node_to_relation(node)
474
470
  self.adapter.cache_added(relation)
475
471
 
476
472
  def relation_exists(self, relation: BaseRelation) -> bool:
@@ -519,24 +515,79 @@ class BaseAdapterExtension(abc.ABC):
519
515
 
520
516
  return data
521
517
 
522
- def _create_cachable_relation(self, node: ManifestNode) -> BaseRelation:
523
- """Create an adapter-specific Relation object suitable for putting into the Relation cache."""
524
- quoting_policy: t.Dict[str, bool] = {}
518
+ def _node_to_normalized_identifiers(
519
+ self,
520
+ node: ManifestNode | SourceDefinition,
521
+ override_database: t.Optional[str] = None,
522
+ override_schema: t.Optional[str] = None,
523
+ ) -> t.Tuple[str, str, str, t.Dict[str, bool]]:
524
+ """Break a dbt manifest node into parts that can be force-quoted without first normalizing.
525
+
526
+ dbt supports quoting the catalog, schema and identifier portions independently whereas SQLGlot
527
+ tends to be all-or-none.
528
+
529
+ The output of this method can be used to construct both dbt Relation objects and SQLGlot exp.Table objects
530
+ that are consistent with each other.
531
+
532
+ :override_database and :override_schema can be used to override the values used for the database and schema portions without
533
+ changing the input node object
534
+
535
+ Returns a tuple of (database, schema, identifier, quote_policy). These are deliberately strings so that they can be used to construct
536
+ a Relation object, but they have been normalized according to the quote policy
537
+ """
538
+ quote_policy = {
539
+ **self.adapter.Relation.get_default_quote_policy().to_dict(omit_none=True),
540
+ **self.adapter.config.quoting,
541
+ **node.config.get("quoting", {}),
542
+ }
543
+
544
+ database = override_database or node.database or self.default_catalog
545
+ schema = override_schema or node.schema
546
+ identifier = str(node.alias if hasattr(node, "alias") else node.identifier)
547
+
548
+ # normalize any unquoted identifiers
549
+ if not quote_policy["database"] and database:
550
+ database = self._sql(self._normalize_identifier(database))
551
+
552
+ if not quote_policy["schema"] and schema:
553
+ schema = self._sql(self._normalize_identifier(schema))
554
+
555
+ if not quote_policy["identifier"] and identifier:
556
+ identifier = self._sql(self._normalize_identifier(identifier))
557
+
558
+ return database, schema, identifier, quote_policy
525
559
 
526
- for policy in (
527
- self.adapter.Relation.get_default_quote_policy().to_dict(omit_none=True),
528
- self.adapter.config.quoting,
529
- node.config.get("quoting", {}),
530
- ):
531
- quoting_policy.update(policy)
560
+ def _node_to_relation(self, node: ManifestNode | SourceDefinition) -> BaseRelation:
561
+ """Turn a dbt manifest node into an adapter-specific Relation object suitable for putting
562
+ into the Relation cache, while respecting the configured quote policy"""
563
+
564
+ database, schema, identifier, dbt_quote_policy = self._node_to_normalized_identifiers(node)
532
565
 
533
566
  return self.adapter.Relation.create(
534
- database=node.database,
535
- schema=node.schema,
536
- identifier=node.identifier,
567
+ database=database,
568
+ schema=schema,
569
+ identifier=identifier,
537
570
  type=RelationType.Table,
538
- quoting=quoting_policy,
571
+ quote_policy=dbt_quote_policy,
572
+ )
573
+
574
+ def _node_to_table(
575
+ self,
576
+ node: ManifestNode | SourceDefinition,
577
+ override_database: t.Optional[str] = None,
578
+ override_schema: t.Optional[str] = None,
579
+ ) -> exp.Table:
580
+ """Given a dbt node, convert it to a SQLGlot exp.Table object while applying correct quoting
581
+ and normalization per the dbt quote policy for this node.
582
+
583
+ Note that since dbt allows a mixture of quoted and unquoted parts and SQLGlot prefers all or nothing,
584
+ the produced SQLGlot exp.Table is semantically equivalent but not identical to the dbt Node.
585
+ This is achieved by normalizing any identifiers that are unquoted on the dbt side and then force-quoting everything
586
+ """
587
+ database, schema, identifier, _ = self._node_to_normalized_identifiers(
588
+ node, override_database=override_database, override_schema=override_schema
539
589
  )
590
+ return exp.table_(catalog=database, db=schema, table=identifier, quoted=True)
540
591
 
541
592
  def get_relation_table_type(
542
593
  self, node: ManifestNode, relation: BaseRelation
@@ -701,12 +752,16 @@ class BaseAdapterExtension(abc.ABC):
701
752
  # since _to_fqn() returns the table parts quoted, we have to normalize unquoted identifiers first
702
753
  # if we force-quote without normalizing then on engines like Snowflake we end up with identifiers
703
754
  # that should be uppercase stuck as lowercase because they were force-quoted
704
- return self._normalize_and_quote_table(
705
- exp.table_(
706
- table_name,
707
- db=table_schema,
708
- catalog=table_catalog,
709
- )
755
+ return quote_identifiers(
756
+ normalize_identifiers(
757
+ exp.table_(
758
+ table_name,
759
+ db=table_schema,
760
+ catalog=table_catalog,
761
+ ),
762
+ dialect=self.dialect,
763
+ ),
764
+ dialect=self.dialect,
710
765
  )
711
766
 
712
767
  def _sql(self, expression: exp.Expr, copy: bool = False) -> str:
@@ -718,12 +773,10 @@ class BaseAdapterExtension(abc.ABC):
718
773
  """
719
774
  return expression.sql(dialect=self.dialect, copy=copy)
720
775
 
721
- def _normalize_and_quote_table(self, table: t.Union[str, exp.Table]) -> exp.Table:
722
- if isinstance(table, str):
723
- table = exp.to_table(table, dialect=self.dialect)
724
- return quote_identifiers(
725
- normalize_identifiers(table, dialect=self.dialect), dialect=self.dialect
726
- )
776
+ def _normalize_identifier(self, expr: t.Union[str, exp.Identifier]) -> exp.Identifier:
777
+ if isinstance(expr, str):
778
+ expr = exp.parse_identifier(expr, dialect=self.dialect)
779
+ return normalize_identifiers(expr, dialect=self.dialect)
727
780
 
728
781
  def _is_system_metadata_table(self, table: exp.Table) -> bool:
729
782
  """Is the specified table a known system metadata table/view?
@@ -18,11 +18,6 @@ from dbt_run_cache.utils import set_invocation_context
18
18
  from query_cache_common.utils import extract_fqn_parts
19
19
  import re
20
20
 
21
- try:
22
- from dbt.adapters.contracts.relation import RelationType
23
- except ImportError:
24
- # dbt 1.7
25
- from dbt.adapters.base.relation import RelationType
26
21
 
27
22
  if t.TYPE_CHECKING:
28
23
  from dbt.contracts.graph.nodes import ManifestNode
@@ -44,28 +39,6 @@ class SnowflakeAdapterExtension(BaseAdapterExtension):
44
39
  def current_timestamp_utc(self) -> datetime:
45
40
  return self.execute("SELECT SYSDATE()", fetch=True).rows[0][0].replace(tzinfo=timezone.utc)
46
41
 
47
- def _create_cachable_relation(self, node: ManifestNode) -> BaseRelation:
48
- from dbt.adapters.snowflake.relation import SnowflakeRelation
49
-
50
- # when building its relation cache, the Snowflake adapter:
51
- # - caches what it gets from Snowflake's INFORMATION_SCHEMA, which is usually uppercase
52
- # - hardcodes the below quoting policy
53
- # so if we are writing our own relations into the cache, we need to normalize them, or we get an ApproximateMatchError when trying to read them back
54
- # ref: https://github.com/dbt-labs/dbt-adapters/blob/60fc903f705f447a7b58d0df6b33d7be7cd00690/dbt-snowflake/src/dbt/adapters/snowflake/impl.py#L321
55
- quoting_policy = {"database": True, "schema": True, "identifier": True}
56
-
57
- normalized = self._normalize_and_quote_table(
58
- self.table_(catalog=node.database, schema=node.schema, table=node.alias, quoted=False)
59
- )
60
-
61
- return SnowflakeRelation.create(
62
- database=normalized.catalog,
63
- schema=normalized.db,
64
- identifier=normalized.name,
65
- type=RelationType.Table,
66
- quoting_policy=quoting_policy,
67
- )
68
-
69
42
  def get_relation_table_type(
70
43
  self, node: ManifestNode, relation: BaseRelation
71
44
  ) -> t.Optional[str]:
@@ -106,6 +106,11 @@ class Explainer:
106
106
  for log_entry in no_decision_entries:
107
107
  self._explain_no_decision_node(log_entry)
108
108
 
109
+ def explain_to_str(self) -> str:
110
+ with self._console.capture() as capture:
111
+ self.explain()
112
+ return capture.get()
113
+
109
114
  def _explain_run_start(self, run_start_entry: RunStartEntry) -> None:
110
115
  self._print("Last run: ")
111
116
 
@@ -1132,19 +1132,16 @@ class RunCache:
1132
1132
  return last_modified_epoch
1133
1133
 
1134
1134
  def _node_to_table(self, node: ManifestNode | SourceDefinition) -> exp.Table:
1135
- database = node.database
1136
- schema = node.schema
1137
- table_name = t.cast(str, node.alias if hasattr(node, "alias") else node.identifier)
1138
- table = self._adapter_ext.table_(table_name, schema, database, quoted=False)
1139
- return self._adapter_ext._normalize_and_quote_table(table)
1135
+ return self._adapter_ext._node_to_table(node)
1140
1136
 
1141
1137
  def _node_to_deferred_table(self, node: ManifestNode) -> exp.Table:
1142
1138
  assert self._deferred_relation_resolver is not None
1143
1139
  database = self._deferred_relation_resolver.get_deferred_database(node) or node.database
1144
1140
  schema = self._deferred_relation_resolver.get_deferred_schema(node) or node.schema
1145
- table_name = node.alias if hasattr(node, "alias") else node.identifier
1146
- table = self._adapter_ext.table_(table_name, schema, database, quoted=False)
1147
- return self._adapter_ext._normalize_and_quote_table(table)
1141
+
1142
+ return self._adapter_ext._node_to_table(
1143
+ node, override_database=database, override_schema=schema
1144
+ )
1148
1145
 
1149
1146
  def _record_cache_hit(
1150
1147
  self,
@@ -1 +0,0 @@
1
- __version__ = "2.2.0"
File without changes
File without changes