pytrilogy 0.3.149__cp313-cp313-win_amd64.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.
Files changed (207) hide show
  1. LICENSE.md +19 -0
  2. _preql_import_resolver/__init__.py +5 -0
  3. _preql_import_resolver/_preql_import_resolver.cp313-win_amd64.pyd +0 -0
  4. pytrilogy-0.3.149.dist-info/METADATA +555 -0
  5. pytrilogy-0.3.149.dist-info/RECORD +207 -0
  6. pytrilogy-0.3.149.dist-info/WHEEL +4 -0
  7. pytrilogy-0.3.149.dist-info/entry_points.txt +2 -0
  8. pytrilogy-0.3.149.dist-info/licenses/LICENSE.md +19 -0
  9. trilogy/__init__.py +27 -0
  10. trilogy/ai/README.md +10 -0
  11. trilogy/ai/__init__.py +19 -0
  12. trilogy/ai/constants.py +92 -0
  13. trilogy/ai/conversation.py +107 -0
  14. trilogy/ai/enums.py +7 -0
  15. trilogy/ai/execute.py +50 -0
  16. trilogy/ai/models.py +34 -0
  17. trilogy/ai/prompts.py +100 -0
  18. trilogy/ai/providers/__init__.py +0 -0
  19. trilogy/ai/providers/anthropic.py +106 -0
  20. trilogy/ai/providers/base.py +24 -0
  21. trilogy/ai/providers/google.py +146 -0
  22. trilogy/ai/providers/openai.py +89 -0
  23. trilogy/ai/providers/utils.py +68 -0
  24. trilogy/authoring/README.md +3 -0
  25. trilogy/authoring/__init__.py +148 -0
  26. trilogy/constants.py +119 -0
  27. trilogy/core/README.md +52 -0
  28. trilogy/core/__init__.py +0 -0
  29. trilogy/core/constants.py +6 -0
  30. trilogy/core/enums.py +454 -0
  31. trilogy/core/env_processor.py +239 -0
  32. trilogy/core/environment_helpers.py +320 -0
  33. trilogy/core/ergonomics.py +193 -0
  34. trilogy/core/exceptions.py +123 -0
  35. trilogy/core/functions.py +1240 -0
  36. trilogy/core/graph_models.py +142 -0
  37. trilogy/core/internal.py +85 -0
  38. trilogy/core/models/__init__.py +0 -0
  39. trilogy/core/models/author.py +2670 -0
  40. trilogy/core/models/build.py +2603 -0
  41. trilogy/core/models/build_environment.py +165 -0
  42. trilogy/core/models/core.py +506 -0
  43. trilogy/core/models/datasource.py +436 -0
  44. trilogy/core/models/environment.py +756 -0
  45. trilogy/core/models/execute.py +1213 -0
  46. trilogy/core/optimization.py +251 -0
  47. trilogy/core/optimizations/__init__.py +12 -0
  48. trilogy/core/optimizations/base_optimization.py +17 -0
  49. trilogy/core/optimizations/hide_unused_concept.py +47 -0
  50. trilogy/core/optimizations/inline_datasource.py +102 -0
  51. trilogy/core/optimizations/predicate_pushdown.py +245 -0
  52. trilogy/core/processing/README.md +94 -0
  53. trilogy/core/processing/READMEv2.md +121 -0
  54. trilogy/core/processing/VIRTUAL_UNNEST.md +30 -0
  55. trilogy/core/processing/__init__.py +0 -0
  56. trilogy/core/processing/concept_strategies_v3.py +508 -0
  57. trilogy/core/processing/constants.py +15 -0
  58. trilogy/core/processing/discovery_node_factory.py +451 -0
  59. trilogy/core/processing/discovery_utility.py +548 -0
  60. trilogy/core/processing/discovery_validation.py +167 -0
  61. trilogy/core/processing/graph_utils.py +43 -0
  62. trilogy/core/processing/node_generators/README.md +9 -0
  63. trilogy/core/processing/node_generators/__init__.py +31 -0
  64. trilogy/core/processing/node_generators/basic_node.py +160 -0
  65. trilogy/core/processing/node_generators/common.py +270 -0
  66. trilogy/core/processing/node_generators/constant_node.py +38 -0
  67. trilogy/core/processing/node_generators/filter_node.py +315 -0
  68. trilogy/core/processing/node_generators/group_node.py +213 -0
  69. trilogy/core/processing/node_generators/group_to_node.py +117 -0
  70. trilogy/core/processing/node_generators/multiselect_node.py +207 -0
  71. trilogy/core/processing/node_generators/node_merge_node.py +695 -0
  72. trilogy/core/processing/node_generators/recursive_node.py +88 -0
  73. trilogy/core/processing/node_generators/rowset_node.py +165 -0
  74. trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  75. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +261 -0
  76. trilogy/core/processing/node_generators/select_merge_node.py +846 -0
  77. trilogy/core/processing/node_generators/select_node.py +95 -0
  78. trilogy/core/processing/node_generators/synonym_node.py +98 -0
  79. trilogy/core/processing/node_generators/union_node.py +91 -0
  80. trilogy/core/processing/node_generators/unnest_node.py +182 -0
  81. trilogy/core/processing/node_generators/window_node.py +201 -0
  82. trilogy/core/processing/nodes/README.md +28 -0
  83. trilogy/core/processing/nodes/__init__.py +179 -0
  84. trilogy/core/processing/nodes/base_node.py +522 -0
  85. trilogy/core/processing/nodes/filter_node.py +75 -0
  86. trilogy/core/processing/nodes/group_node.py +194 -0
  87. trilogy/core/processing/nodes/merge_node.py +420 -0
  88. trilogy/core/processing/nodes/recursive_node.py +46 -0
  89. trilogy/core/processing/nodes/select_node_v2.py +242 -0
  90. trilogy/core/processing/nodes/union_node.py +53 -0
  91. trilogy/core/processing/nodes/unnest_node.py +62 -0
  92. trilogy/core/processing/nodes/window_node.py +56 -0
  93. trilogy/core/processing/utility.py +823 -0
  94. trilogy/core/query_processor.py +604 -0
  95. trilogy/core/statements/README.md +35 -0
  96. trilogy/core/statements/__init__.py +0 -0
  97. trilogy/core/statements/author.py +536 -0
  98. trilogy/core/statements/build.py +0 -0
  99. trilogy/core/statements/common.py +20 -0
  100. trilogy/core/statements/execute.py +155 -0
  101. trilogy/core/table_processor.py +66 -0
  102. trilogy/core/utility.py +8 -0
  103. trilogy/core/validation/README.md +46 -0
  104. trilogy/core/validation/__init__.py +0 -0
  105. trilogy/core/validation/common.py +161 -0
  106. trilogy/core/validation/concept.py +146 -0
  107. trilogy/core/validation/datasource.py +227 -0
  108. trilogy/core/validation/environment.py +73 -0
  109. trilogy/core/validation/fix.py +256 -0
  110. trilogy/dialect/__init__.py +32 -0
  111. trilogy/dialect/base.py +1432 -0
  112. trilogy/dialect/bigquery.py +314 -0
  113. trilogy/dialect/common.py +147 -0
  114. trilogy/dialect/config.py +159 -0
  115. trilogy/dialect/dataframe.py +50 -0
  116. trilogy/dialect/duckdb.py +397 -0
  117. trilogy/dialect/enums.py +151 -0
  118. trilogy/dialect/metadata.py +173 -0
  119. trilogy/dialect/mock.py +190 -0
  120. trilogy/dialect/postgres.py +117 -0
  121. trilogy/dialect/presto.py +110 -0
  122. trilogy/dialect/results.py +89 -0
  123. trilogy/dialect/snowflake.py +129 -0
  124. trilogy/dialect/sql_server.py +137 -0
  125. trilogy/engine.py +48 -0
  126. trilogy/execution/__init__.py +17 -0
  127. trilogy/execution/config.py +119 -0
  128. trilogy/execution/state/__init__.py +0 -0
  129. trilogy/execution/state/exceptions.py +26 -0
  130. trilogy/execution/state/file_state_store.py +0 -0
  131. trilogy/execution/state/sqllite_state_store.py +0 -0
  132. trilogy/execution/state/state_store.py +406 -0
  133. trilogy/executor.py +692 -0
  134. trilogy/hooks/__init__.py +4 -0
  135. trilogy/hooks/base_hook.py +40 -0
  136. trilogy/hooks/graph_hook.py +135 -0
  137. trilogy/hooks/query_debugger.py +166 -0
  138. trilogy/metadata/__init__.py +0 -0
  139. trilogy/parser.py +10 -0
  140. trilogy/parsing/README.md +21 -0
  141. trilogy/parsing/__init__.py +0 -0
  142. trilogy/parsing/common.py +1069 -0
  143. trilogy/parsing/config.py +5 -0
  144. trilogy/parsing/exceptions.py +8 -0
  145. trilogy/parsing/helpers.py +1 -0
  146. trilogy/parsing/parse_engine.py +2876 -0
  147. trilogy/parsing/render.py +775 -0
  148. trilogy/parsing/trilogy.lark +546 -0
  149. trilogy/py.typed +0 -0
  150. trilogy/render.py +45 -0
  151. trilogy/scripts/README.md +9 -0
  152. trilogy/scripts/__init__.py +0 -0
  153. trilogy/scripts/agent.py +41 -0
  154. trilogy/scripts/agent_info.py +306 -0
  155. trilogy/scripts/common.py +432 -0
  156. trilogy/scripts/dependency/Cargo.lock +617 -0
  157. trilogy/scripts/dependency/Cargo.toml +39 -0
  158. trilogy/scripts/dependency/README.md +131 -0
  159. trilogy/scripts/dependency/build.sh +25 -0
  160. trilogy/scripts/dependency/src/directory_resolver.rs +387 -0
  161. trilogy/scripts/dependency/src/lib.rs +16 -0
  162. trilogy/scripts/dependency/src/main.rs +770 -0
  163. trilogy/scripts/dependency/src/parser.rs +435 -0
  164. trilogy/scripts/dependency/src/preql.pest +208 -0
  165. trilogy/scripts/dependency/src/python_bindings.rs +311 -0
  166. trilogy/scripts/dependency/src/resolver.rs +716 -0
  167. trilogy/scripts/dependency/tests/base.preql +3 -0
  168. trilogy/scripts/dependency/tests/cli_integration.rs +377 -0
  169. trilogy/scripts/dependency/tests/customer.preql +6 -0
  170. trilogy/scripts/dependency/tests/main.preql +9 -0
  171. trilogy/scripts/dependency/tests/orders.preql +7 -0
  172. trilogy/scripts/dependency/tests/test_data/base.preql +9 -0
  173. trilogy/scripts/dependency/tests/test_data/consumer.preql +1 -0
  174. trilogy/scripts/dependency.py +323 -0
  175. trilogy/scripts/display.py +555 -0
  176. trilogy/scripts/environment.py +59 -0
  177. trilogy/scripts/fmt.py +32 -0
  178. trilogy/scripts/ingest.py +487 -0
  179. trilogy/scripts/ingest_helpers/__init__.py +1 -0
  180. trilogy/scripts/ingest_helpers/foreign_keys.py +123 -0
  181. trilogy/scripts/ingest_helpers/formatting.py +93 -0
  182. trilogy/scripts/ingest_helpers/typing.py +161 -0
  183. trilogy/scripts/init.py +105 -0
  184. trilogy/scripts/parallel_execution.py +762 -0
  185. trilogy/scripts/plan.py +189 -0
  186. trilogy/scripts/refresh.py +161 -0
  187. trilogy/scripts/run.py +79 -0
  188. trilogy/scripts/serve.py +202 -0
  189. trilogy/scripts/serve_helpers/__init__.py +41 -0
  190. trilogy/scripts/serve_helpers/file_discovery.py +142 -0
  191. trilogy/scripts/serve_helpers/index_generation.py +206 -0
  192. trilogy/scripts/serve_helpers/models.py +38 -0
  193. trilogy/scripts/single_execution.py +131 -0
  194. trilogy/scripts/testing.py +143 -0
  195. trilogy/scripts/trilogy.py +75 -0
  196. trilogy/std/__init__.py +0 -0
  197. trilogy/std/color.preql +3 -0
  198. trilogy/std/date.preql +13 -0
  199. trilogy/std/display.preql +18 -0
  200. trilogy/std/geography.preql +22 -0
  201. trilogy/std/metric.preql +15 -0
  202. trilogy/std/money.preql +67 -0
  203. trilogy/std/net.preql +14 -0
  204. trilogy/std/ranking.preql +7 -0
  205. trilogy/std/report.preql +5 -0
  206. trilogy/std/semantic.preql +6 -0
  207. trilogy/utility.py +34 -0
@@ -0,0 +1,436 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import date, datetime
3
+ from enum import Enum
4
+ from typing import TYPE_CHECKING, ItemsView, List, Optional, Union, ValuesView
5
+
6
+ from pydantic import BaseModel, Field, ValidationInfo, field_validator
7
+
8
+ from trilogy.constants import DEFAULT_NAMESPACE, MagicConstants, logger
9
+ from trilogy.core.enums import (
10
+ AddressType,
11
+ BooleanOperator,
12
+ ComparisonOperator,
13
+ DatasourceState,
14
+ Modifier,
15
+ )
16
+ from trilogy.core.models.author import (
17
+ Comparison,
18
+ Concept,
19
+ ConceptRef,
20
+ Conditional,
21
+ Function,
22
+ Grain,
23
+ HasUUID,
24
+ LooseConceptList,
25
+ Namespaced,
26
+ WhereClause,
27
+ )
28
+
29
+ LOGGER_PREFIX = "[MODELS_DATASOURCE]"
30
+
31
+ if TYPE_CHECKING:
32
+ from trilogy.core.models.environment import Environment
33
+ from trilogy.core.statements.author import SelectStatement
34
+
35
+
36
+ class UpdateKeyType(Enum):
37
+ INCREMENTAL_KEY = "incremental_key"
38
+ UPDATE_TIME = "update_time"
39
+ KEY_HASH = "key_hash"
40
+
41
+
42
+ @dataclass
43
+ class UpdateKey:
44
+ """Represents a key used to track data freshness for incremental updates."""
45
+
46
+ concept_name: str
47
+ type: UpdateKeyType
48
+ value: str | int | float | datetime | date | None
49
+
50
+ def to_comparison(self, environment: "Environment") -> "Comparison":
51
+ """Convert this update key to a Comparison for use in WHERE clauses."""
52
+
53
+ concept = environment.concepts[self.concept_name]
54
+ right_value = self.value if self.value is not None else MagicConstants.NULL
55
+ return Comparison(
56
+ left=concept.reference,
57
+ right=right_value,
58
+ operator=ComparisonOperator.GT,
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class UpdateKeys:
64
+ """Collection of update keys for a datasource."""
65
+
66
+ keys: dict[str, UpdateKey] = field(default_factory=dict)
67
+
68
+ def to_where_clause(self, environment: "Environment") -> WhereClause | None:
69
+ """Convert update keys to a WhereClause for filtering."""
70
+
71
+ comparisons = [
72
+ key.to_comparison(environment)
73
+ for key in self.keys.values()
74
+ if key.value is not None
75
+ ]
76
+ if not comparisons:
77
+ return None
78
+ if len(comparisons) == 1:
79
+ return WhereClause(conditional=comparisons[0])
80
+ conditional = Conditional(
81
+ left=comparisons[0],
82
+ right=comparisons[1],
83
+ operator=BooleanOperator.AND,
84
+ )
85
+ for comp in comparisons[2:]:
86
+ conditional = Conditional(
87
+ left=conditional, right=comp, operator=BooleanOperator.AND
88
+ )
89
+ return WhereClause(conditional=conditional)
90
+
91
+
92
+ class RawColumnExpr(BaseModel):
93
+ text: str
94
+
95
+
96
+ class ColumnAssignment(BaseModel):
97
+ alias: str | RawColumnExpr | Function
98
+ concept: ConceptRef
99
+ modifiers: List[Modifier] = Field(default_factory=list)
100
+
101
+ @field_validator("concept", mode="before")
102
+ def force_reference(cls, v: ConceptRef, info: ValidationInfo):
103
+ if isinstance(v, Concept):
104
+ return v.reference
105
+ return v
106
+
107
+ def __eq__(self, other):
108
+ if not isinstance(other, ColumnAssignment):
109
+ return False
110
+ return (
111
+ self.alias == other.alias
112
+ and self.concept == other.concept
113
+ and self.modifiers == other.modifiers
114
+ )
115
+
116
+ @property
117
+ def is_concrete(self) -> bool:
118
+ return isinstance(self.alias, str)
119
+
120
+ @property
121
+ def is_complete(self) -> bool:
122
+ return Modifier.PARTIAL not in self.modifiers
123
+
124
+ @property
125
+ def is_nullable(self) -> bool:
126
+ return Modifier.NULLABLE in self.modifiers
127
+
128
+ def with_namespace(self, namespace: str) -> "ColumnAssignment":
129
+ return ColumnAssignment.model_construct(
130
+ alias=(
131
+ self.alias.with_namespace(namespace)
132
+ if isinstance(self.alias, Function)
133
+ else self.alias
134
+ ),
135
+ concept=self.concept.with_namespace(namespace),
136
+ modifiers=self.modifiers,
137
+ )
138
+
139
+ def with_merge(
140
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
141
+ ) -> "ColumnAssignment":
142
+ return ColumnAssignment.model_construct(
143
+ alias=self.alias,
144
+ concept=self.concept.with_merge(source, target, modifiers),
145
+ modifiers=(
146
+ modifiers if self.concept.address == source.address else self.modifiers
147
+ ),
148
+ )
149
+
150
+
151
+ class Address(BaseModel):
152
+ location: str
153
+ quoted: bool = False
154
+ type: AddressType = AddressType.TABLE
155
+
156
+ @property
157
+ def is_query(self):
158
+ return self.type == AddressType.QUERY
159
+
160
+ @property
161
+ def is_file(self):
162
+ return self.type in {
163
+ AddressType.PYTHON_SCRIPT,
164
+ AddressType.CSV,
165
+ AddressType.TSV,
166
+ AddressType.PARQUET,
167
+ AddressType.SQL,
168
+ }
169
+
170
+
171
+ @dataclass
172
+ class Query:
173
+ text: str
174
+
175
+
176
+ @dataclass
177
+ class File:
178
+ path: str
179
+ type: AddressType
180
+
181
+
182
+ class DatasourceMetadata(BaseModel):
183
+ freshness_concept: Concept | None
184
+ partition_fields: List[Concept] = Field(default_factory=list)
185
+ line_no: int | None = None
186
+
187
+
188
+ def safe_grain(v) -> Grain:
189
+ if isinstance(v, dict):
190
+ return Grain.model_validate(v)
191
+ elif isinstance(v, Grain):
192
+ return v
193
+ elif not v:
194
+ return Grain(components=set())
195
+ else:
196
+ raise ValueError(f"Invalid input type to safe_grain {type(v)}")
197
+
198
+
199
+ class Datasource(HasUUID, Namespaced, BaseModel):
200
+ name: str
201
+ columns: List[ColumnAssignment]
202
+ address: Union[Address, str]
203
+ grain: Grain = Field(
204
+ default_factory=lambda: Grain(components=set()), validate_default=True
205
+ )
206
+ namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
207
+ metadata: DatasourceMetadata = Field(
208
+ default_factory=lambda: DatasourceMetadata(freshness_concept=None)
209
+ )
210
+ where: Optional[WhereClause] = None
211
+ non_partial_for: Optional[WhereClause] = None
212
+ status: DatasourceState = Field(default=DatasourceState.PUBLISHED)
213
+ incremental_by: List[ConceptRef] = Field(default_factory=list)
214
+ partition_by: List[ConceptRef] = Field(default_factory=list)
215
+ freshness_by: List[ConceptRef] = Field(default_factory=list)
216
+ is_root: bool = False
217
+
218
+ @property
219
+ def safe_address(self) -> str:
220
+ if isinstance(self.address, Address):
221
+ return self.address.location
222
+ return self.address
223
+
224
+ def __eq__(self, other):
225
+ if not isinstance(other, Datasource):
226
+ return False
227
+ return (
228
+ self.name == other.name
229
+ and self.namespace == other.namespace
230
+ and self.grain == other.grain
231
+ and self.address == other.address
232
+ and self.where == other.where
233
+ and self.columns == other.columns
234
+ and self.non_partial_for == other.non_partial_for
235
+ )
236
+
237
+ def duplicate(self) -> "Datasource":
238
+ return self.model_copy(deep=True)
239
+
240
+ @property
241
+ def concrete_columns(self) -> dict[str, ColumnAssignment]:
242
+ return {c.alias: c for c in self.columns if c.is_concrete} # type: ignore[misc]
243
+
244
+ @property
245
+ def hidden_concepts(self) -> List[Concept]:
246
+ return []
247
+
248
+ def merge_concept(
249
+ self, source: Concept, target: Concept, modifiers: List[Modifier]
250
+ ):
251
+ original = [c for c in self.columns if c.concept.address == source.address]
252
+ early_exit_check = [
253
+ c for c in self.columns if c.concept.address == target.address
254
+ ]
255
+ if early_exit_check:
256
+ logger.info(
257
+ f"No concept merge needed on merge of {source} to {target}, have {[x.concept.address for x in self.columns]}"
258
+ )
259
+ return None
260
+ if len(original) != 1:
261
+ raise ValueError(
262
+ f"Expected exactly one column to merge, got {len(original)} for {source.address}, {[x.alias for x in original]}"
263
+ )
264
+ # map to the alias with the modifier, and the original
265
+ self.columns = [
266
+ c.with_merge(source, target, modifiers)
267
+ for c in self.columns
268
+ if c.concept.address != source.address
269
+ ] + original
270
+ self.grain = self.grain.with_merge(source, target, modifiers)
271
+ self.where = (
272
+ self.where.with_merge(source, target, modifiers) if self.where else None
273
+ )
274
+
275
+ self.add_column(target, original[0].alias, modifiers)
276
+
277
+ @property
278
+ def identifier(self) -> str:
279
+ if not self.namespace or self.namespace == DEFAULT_NAMESPACE:
280
+ return self.name
281
+ return f"{self.namespace}.{self.name}"
282
+
283
+ @property
284
+ def safe_identifier(self) -> str:
285
+ return self.identifier.replace(".", "_")
286
+
287
+ @property
288
+ def output_lcl(self) -> LooseConceptList:
289
+ return LooseConceptList(concepts=self.output_concepts)
290
+
291
+ @property
292
+ def non_partial_concept_addresses(self) -> set[str]:
293
+ return set([c.address for c in self.full_concepts])
294
+
295
+ @field_validator("namespace", mode="plain")
296
+ @classmethod
297
+ def namespace_validation(cls, v):
298
+ return v or DEFAULT_NAMESPACE
299
+
300
+ @field_validator("address")
301
+ @classmethod
302
+ def address_enforcement(cls, v):
303
+ if isinstance(v, str):
304
+ v = Address(location=v)
305
+ return v
306
+
307
+ @field_validator("grain", mode="before")
308
+ @classmethod
309
+ def grain_enforcement(cls, v: Grain, info: ValidationInfo):
310
+ grain: Grain = safe_grain(v)
311
+ return grain
312
+
313
+ def add_column(
314
+ self,
315
+ concept: Concept,
316
+ alias: str | RawColumnExpr | Function,
317
+ modifiers: List[Modifier] | None = None,
318
+ ):
319
+ self.columns.append(
320
+ ColumnAssignment(
321
+ alias=alias, concept=concept.reference, modifiers=modifiers or []
322
+ )
323
+ )
324
+
325
+ def __add__(self, other):
326
+ if not other == self:
327
+ raise ValueError(
328
+ "Attempted to add two datasources that are not identical, this is not a valid operation"
329
+ )
330
+ return self
331
+
332
+ def __repr__(self):
333
+ return f"Datasource<{self.identifier}@<{self.grain}>"
334
+
335
+ def __str__(self):
336
+ return self.__repr__()
337
+
338
+ def __hash__(self):
339
+ return self.identifier.__hash__()
340
+
341
+ def with_namespace(self, namespace: str):
342
+ new_namespace = (
343
+ namespace + "." + self.namespace
344
+ if self.namespace and self.namespace != DEFAULT_NAMESPACE
345
+ else namespace
346
+ )
347
+ new = Datasource.model_construct(
348
+ name=self.name,
349
+ namespace=new_namespace,
350
+ grain=self.grain.with_namespace(namespace),
351
+ address=self.address,
352
+ columns=[c.with_namespace(namespace) for c in self.columns],
353
+ where=self.where.with_namespace(namespace) if self.where else None,
354
+ non_partial_for=(
355
+ self.non_partial_for.with_namespace(namespace)
356
+ if self.non_partial_for
357
+ else None
358
+ ),
359
+ status=self.status,
360
+ incremental_by=[c.with_namespace(namespace) for c in self.incremental_by],
361
+ partition_by=[c.with_namespace(namespace) for c in self.partition_by],
362
+ freshness_by=[c.with_namespace(namespace) for c in self.freshness_by],
363
+ is_root=self.is_root,
364
+ )
365
+ return new
366
+
367
+ def create_update_statement(
368
+ self,
369
+ environment: "Environment",
370
+ where: Optional[WhereClause] = None,
371
+ line_no: int | None = None,
372
+ ) -> "SelectStatement":
373
+ from trilogy.core.statements.author import Metadata, SelectItem, SelectStatement
374
+
375
+ return SelectStatement.from_inputs(
376
+ environment=environment,
377
+ selection=[
378
+ SelectItem(
379
+ content=ConceptRef(address=col.concept.address),
380
+ modifiers=[],
381
+ )
382
+ for col in self.columns
383
+ ],
384
+ where_clause=where,
385
+ meta=Metadata(line_number=line_no) if line_no else None,
386
+ )
387
+
388
+ @property
389
+ def concepts(self) -> List[ConceptRef]:
390
+ return [c.concept for c in self.columns]
391
+
392
+ @property
393
+ def group_required(self):
394
+ return False
395
+
396
+ @property
397
+ def full_concepts(self) -> List[ConceptRef]:
398
+ return [c.concept for c in self.columns if Modifier.PARTIAL not in c.modifiers]
399
+
400
+ @property
401
+ def nullable_concepts(self) -> List[ConceptRef]:
402
+ return [c.concept for c in self.columns if Modifier.NULLABLE in c.modifiers]
403
+
404
+ @property
405
+ def output_concepts(self) -> List[ConceptRef]:
406
+ return self.concepts
407
+
408
+ @property
409
+ def partial_concepts(self) -> List[ConceptRef]:
410
+ return [c.concept for c in self.columns if Modifier.PARTIAL in c.modifiers]
411
+
412
+
413
+ class EnvironmentDatasourceDict(dict):
414
+ def __init__(self, *args, **kwargs) -> None:
415
+ super().__init__(self, *args, **kwargs)
416
+
417
+ def __getitem__(self, key: str) -> Datasource:
418
+ try:
419
+ return super(EnvironmentDatasourceDict, self).__getitem__(key)
420
+ except KeyError:
421
+ if DEFAULT_NAMESPACE + "." + key in self:
422
+ return self.__getitem__(DEFAULT_NAMESPACE + "." + key)
423
+ if "." in key and key.split(".", 1)[0] == DEFAULT_NAMESPACE:
424
+ return self.__getitem__(key.split(".", 1)[1])
425
+ raise
426
+
427
+ def values(self) -> ValuesView[Datasource]: # type: ignore
428
+ return super().values()
429
+
430
+ def items(self) -> ItemsView[str, Datasource]: # type: ignore
431
+ return super().items()
432
+
433
+ def duplicate(self) -> "EnvironmentDatasourceDict":
434
+ new = EnvironmentDatasourceDict()
435
+ new.update({k: v.duplicate() for k, v in self.items()})
436
+ return new