dvt-core 1.11.0b4__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 dvt-core might be problematic. Click here for more details.

Files changed (261) hide show
  1. dvt/__init__.py +7 -0
  2. dvt/_pydantic_shim.py +26 -0
  3. dvt/adapters/__init__.py +16 -0
  4. dvt/adapters/multi_adapter_manager.py +268 -0
  5. dvt/artifacts/__init__.py +0 -0
  6. dvt/artifacts/exceptions/__init__.py +1 -0
  7. dvt/artifacts/exceptions/schemas.py +31 -0
  8. dvt/artifacts/resources/__init__.py +116 -0
  9. dvt/artifacts/resources/base.py +68 -0
  10. dvt/artifacts/resources/types.py +93 -0
  11. dvt/artifacts/resources/v1/analysis.py +10 -0
  12. dvt/artifacts/resources/v1/catalog.py +23 -0
  13. dvt/artifacts/resources/v1/components.py +275 -0
  14. dvt/artifacts/resources/v1/config.py +282 -0
  15. dvt/artifacts/resources/v1/documentation.py +11 -0
  16. dvt/artifacts/resources/v1/exposure.py +52 -0
  17. dvt/artifacts/resources/v1/function.py +53 -0
  18. dvt/artifacts/resources/v1/generic_test.py +32 -0
  19. dvt/artifacts/resources/v1/group.py +22 -0
  20. dvt/artifacts/resources/v1/hook.py +11 -0
  21. dvt/artifacts/resources/v1/macro.py +30 -0
  22. dvt/artifacts/resources/v1/metric.py +173 -0
  23. dvt/artifacts/resources/v1/model.py +146 -0
  24. dvt/artifacts/resources/v1/owner.py +10 -0
  25. dvt/artifacts/resources/v1/saved_query.py +112 -0
  26. dvt/artifacts/resources/v1/seed.py +42 -0
  27. dvt/artifacts/resources/v1/semantic_layer_components.py +72 -0
  28. dvt/artifacts/resources/v1/semantic_model.py +315 -0
  29. dvt/artifacts/resources/v1/singular_test.py +14 -0
  30. dvt/artifacts/resources/v1/snapshot.py +92 -0
  31. dvt/artifacts/resources/v1/source_definition.py +85 -0
  32. dvt/artifacts/resources/v1/sql_operation.py +10 -0
  33. dvt/artifacts/resources/v1/unit_test_definition.py +78 -0
  34. dvt/artifacts/schemas/__init__.py +0 -0
  35. dvt/artifacts/schemas/base.py +191 -0
  36. dvt/artifacts/schemas/batch_results.py +24 -0
  37. dvt/artifacts/schemas/catalog/__init__.py +12 -0
  38. dvt/artifacts/schemas/catalog/v1/__init__.py +0 -0
  39. dvt/artifacts/schemas/catalog/v1/catalog.py +60 -0
  40. dvt/artifacts/schemas/freshness/__init__.py +1 -0
  41. dvt/artifacts/schemas/freshness/v3/__init__.py +0 -0
  42. dvt/artifacts/schemas/freshness/v3/freshness.py +159 -0
  43. dvt/artifacts/schemas/manifest/__init__.py +2 -0
  44. dvt/artifacts/schemas/manifest/v12/__init__.py +0 -0
  45. dvt/artifacts/schemas/manifest/v12/manifest.py +212 -0
  46. dvt/artifacts/schemas/results.py +148 -0
  47. dvt/artifacts/schemas/run/__init__.py +2 -0
  48. dvt/artifacts/schemas/run/v5/__init__.py +0 -0
  49. dvt/artifacts/schemas/run/v5/run.py +184 -0
  50. dvt/artifacts/schemas/upgrades/__init__.py +4 -0
  51. dvt/artifacts/schemas/upgrades/upgrade_manifest.py +174 -0
  52. dvt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py +2 -0
  53. dvt/artifacts/utils/validation.py +153 -0
  54. dvt/cli/__init__.py +1 -0
  55. dvt/cli/context.py +16 -0
  56. dvt/cli/exceptions.py +56 -0
  57. dvt/cli/flags.py +558 -0
  58. dvt/cli/main.py +971 -0
  59. dvt/cli/option_types.py +121 -0
  60. dvt/cli/options.py +79 -0
  61. dvt/cli/params.py +803 -0
  62. dvt/cli/requires.py +478 -0
  63. dvt/cli/resolvers.py +32 -0
  64. dvt/cli/types.py +40 -0
  65. dvt/clients/__init__.py +0 -0
  66. dvt/clients/checked_load.py +82 -0
  67. dvt/clients/git.py +164 -0
  68. dvt/clients/jinja.py +206 -0
  69. dvt/clients/jinja_static.py +245 -0
  70. dvt/clients/registry.py +192 -0
  71. dvt/clients/yaml_helper.py +68 -0
  72. dvt/compilation.py +833 -0
  73. dvt/compute/__init__.py +26 -0
  74. dvt/compute/base.py +288 -0
  75. dvt/compute/engines/__init__.py +13 -0
  76. dvt/compute/engines/duckdb_engine.py +368 -0
  77. dvt/compute/engines/spark_engine.py +273 -0
  78. dvt/compute/query_analyzer.py +212 -0
  79. dvt/compute/router.py +483 -0
  80. dvt/config/__init__.py +4 -0
  81. dvt/config/catalogs.py +95 -0
  82. dvt/config/compute_config.py +406 -0
  83. dvt/config/profile.py +411 -0
  84. dvt/config/profiles_v2.py +464 -0
  85. dvt/config/project.py +893 -0
  86. dvt/config/renderer.py +232 -0
  87. dvt/config/runtime.py +491 -0
  88. dvt/config/selectors.py +209 -0
  89. dvt/config/utils.py +78 -0
  90. dvt/connectors/.gitignore +6 -0
  91. dvt/connectors/README.md +306 -0
  92. dvt/connectors/catalog.yml +217 -0
  93. dvt/connectors/download_connectors.py +300 -0
  94. dvt/constants.py +29 -0
  95. dvt/context/__init__.py +0 -0
  96. dvt/context/base.py +746 -0
  97. dvt/context/configured.py +136 -0
  98. dvt/context/context_config.py +350 -0
  99. dvt/context/docs.py +82 -0
  100. dvt/context/exceptions_jinja.py +179 -0
  101. dvt/context/macro_resolver.py +195 -0
  102. dvt/context/macros.py +171 -0
  103. dvt/context/manifest.py +73 -0
  104. dvt/context/providers.py +2198 -0
  105. dvt/context/query_header.py +14 -0
  106. dvt/context/secret.py +59 -0
  107. dvt/context/target.py +74 -0
  108. dvt/contracts/__init__.py +0 -0
  109. dvt/contracts/files.py +413 -0
  110. dvt/contracts/graph/__init__.py +0 -0
  111. dvt/contracts/graph/manifest.py +1904 -0
  112. dvt/contracts/graph/metrics.py +98 -0
  113. dvt/contracts/graph/model_config.py +71 -0
  114. dvt/contracts/graph/node_args.py +42 -0
  115. dvt/contracts/graph/nodes.py +1806 -0
  116. dvt/contracts/graph/semantic_manifest.py +233 -0
  117. dvt/contracts/graph/unparsed.py +812 -0
  118. dvt/contracts/project.py +417 -0
  119. dvt/contracts/results.py +53 -0
  120. dvt/contracts/selection.py +23 -0
  121. dvt/contracts/sql.py +86 -0
  122. dvt/contracts/state.py +69 -0
  123. dvt/contracts/util.py +46 -0
  124. dvt/deprecations.py +347 -0
  125. dvt/deps/__init__.py +0 -0
  126. dvt/deps/base.py +153 -0
  127. dvt/deps/git.py +196 -0
  128. dvt/deps/local.py +80 -0
  129. dvt/deps/registry.py +131 -0
  130. dvt/deps/resolver.py +149 -0
  131. dvt/deps/tarball.py +121 -0
  132. dvt/docs/source/_ext/dbt_click.py +118 -0
  133. dvt/docs/source/conf.py +32 -0
  134. dvt/env_vars.py +64 -0
  135. dvt/event_time/event_time.py +40 -0
  136. dvt/event_time/sample_window.py +60 -0
  137. dvt/events/__init__.py +16 -0
  138. dvt/events/base_types.py +37 -0
  139. dvt/events/core_types_pb2.py +2 -0
  140. dvt/events/logging.py +109 -0
  141. dvt/events/types.py +2534 -0
  142. dvt/exceptions.py +1487 -0
  143. dvt/flags.py +89 -0
  144. dvt/graph/__init__.py +11 -0
  145. dvt/graph/cli.py +248 -0
  146. dvt/graph/graph.py +172 -0
  147. dvt/graph/queue.py +213 -0
  148. dvt/graph/selector.py +375 -0
  149. dvt/graph/selector_methods.py +976 -0
  150. dvt/graph/selector_spec.py +223 -0
  151. dvt/graph/thread_pool.py +18 -0
  152. dvt/hooks.py +21 -0
  153. dvt/include/README.md +49 -0
  154. dvt/include/__init__.py +3 -0
  155. dvt/include/global_project.py +4 -0
  156. dvt/include/starter_project/.gitignore +4 -0
  157. dvt/include/starter_project/README.md +15 -0
  158. dvt/include/starter_project/__init__.py +3 -0
  159. dvt/include/starter_project/analyses/.gitkeep +0 -0
  160. dvt/include/starter_project/dvt_project.yml +36 -0
  161. dvt/include/starter_project/macros/.gitkeep +0 -0
  162. dvt/include/starter_project/models/example/my_first_dbt_model.sql +27 -0
  163. dvt/include/starter_project/models/example/my_second_dbt_model.sql +6 -0
  164. dvt/include/starter_project/models/example/schema.yml +21 -0
  165. dvt/include/starter_project/seeds/.gitkeep +0 -0
  166. dvt/include/starter_project/snapshots/.gitkeep +0 -0
  167. dvt/include/starter_project/tests/.gitkeep +0 -0
  168. dvt/internal_deprecations.py +27 -0
  169. dvt/jsonschemas/__init__.py +3 -0
  170. dvt/jsonschemas/jsonschemas.py +309 -0
  171. dvt/jsonschemas/project/0.0.110.json +4717 -0
  172. dvt/jsonschemas/project/0.0.85.json +2015 -0
  173. dvt/jsonschemas/resources/0.0.110.json +2636 -0
  174. dvt/jsonschemas/resources/0.0.85.json +2536 -0
  175. dvt/jsonschemas/resources/latest.json +6773 -0
  176. dvt/links.py +4 -0
  177. dvt/materializations/__init__.py +0 -0
  178. dvt/materializations/incremental/__init__.py +0 -0
  179. dvt/materializations/incremental/microbatch.py +235 -0
  180. dvt/mp_context.py +8 -0
  181. dvt/node_types.py +37 -0
  182. dvt/parser/__init__.py +23 -0
  183. dvt/parser/analysis.py +21 -0
  184. dvt/parser/base.py +549 -0
  185. dvt/parser/common.py +267 -0
  186. dvt/parser/docs.py +52 -0
  187. dvt/parser/fixtures.py +51 -0
  188. dvt/parser/functions.py +30 -0
  189. dvt/parser/generic_test.py +100 -0
  190. dvt/parser/generic_test_builders.py +334 -0
  191. dvt/parser/hooks.py +119 -0
  192. dvt/parser/macros.py +137 -0
  193. dvt/parser/manifest.py +2204 -0
  194. dvt/parser/models.py +574 -0
  195. dvt/parser/partial.py +1179 -0
  196. dvt/parser/read_files.py +445 -0
  197. dvt/parser/schema_generic_tests.py +423 -0
  198. dvt/parser/schema_renderer.py +111 -0
  199. dvt/parser/schema_yaml_readers.py +936 -0
  200. dvt/parser/schemas.py +1467 -0
  201. dvt/parser/search.py +149 -0
  202. dvt/parser/seeds.py +28 -0
  203. dvt/parser/singular_test.py +20 -0
  204. dvt/parser/snapshots.py +44 -0
  205. dvt/parser/sources.py +557 -0
  206. dvt/parser/sql.py +63 -0
  207. dvt/parser/unit_tests.py +622 -0
  208. dvt/plugins/__init__.py +20 -0
  209. dvt/plugins/contracts.py +10 -0
  210. dvt/plugins/exceptions.py +2 -0
  211. dvt/plugins/manager.py +164 -0
  212. dvt/plugins/manifest.py +21 -0
  213. dvt/profiler.py +20 -0
  214. dvt/py.typed +1 -0
  215. dvt/runners/__init__.py +2 -0
  216. dvt/runners/exposure_runner.py +7 -0
  217. dvt/runners/no_op_runner.py +46 -0
  218. dvt/runners/saved_query_runner.py +7 -0
  219. dvt/selected_resources.py +8 -0
  220. dvt/task/__init__.py +0 -0
  221. dvt/task/base.py +504 -0
  222. dvt/task/build.py +197 -0
  223. dvt/task/clean.py +57 -0
  224. dvt/task/clone.py +162 -0
  225. dvt/task/compile.py +151 -0
  226. dvt/task/compute.py +366 -0
  227. dvt/task/debug.py +650 -0
  228. dvt/task/deps.py +280 -0
  229. dvt/task/docs/__init__.py +3 -0
  230. dvt/task/docs/generate.py +408 -0
  231. dvt/task/docs/index.html +250 -0
  232. dvt/task/docs/serve.py +28 -0
  233. dvt/task/freshness.py +323 -0
  234. dvt/task/function.py +122 -0
  235. dvt/task/group_lookup.py +46 -0
  236. dvt/task/init.py +374 -0
  237. dvt/task/list.py +237 -0
  238. dvt/task/printer.py +176 -0
  239. dvt/task/profiles.py +256 -0
  240. dvt/task/retry.py +175 -0
  241. dvt/task/run.py +1146 -0
  242. dvt/task/run_operation.py +142 -0
  243. dvt/task/runnable.py +802 -0
  244. dvt/task/seed.py +104 -0
  245. dvt/task/show.py +150 -0
  246. dvt/task/snapshot.py +57 -0
  247. dvt/task/sql.py +111 -0
  248. dvt/task/test.py +464 -0
  249. dvt/tests/fixtures/__init__.py +1 -0
  250. dvt/tests/fixtures/project.py +620 -0
  251. dvt/tests/util.py +651 -0
  252. dvt/tracking.py +529 -0
  253. dvt/utils/__init__.py +3 -0
  254. dvt/utils/artifact_upload.py +151 -0
  255. dvt/utils/utils.py +408 -0
  256. dvt/version.py +249 -0
  257. dvt_core-1.11.0b4.dist-info/METADATA +252 -0
  258. dvt_core-1.11.0b4.dist-info/RECORD +261 -0
  259. dvt_core-1.11.0b4.dist-info/WHEEL +5 -0
  260. dvt_core-1.11.0b4.dist-info/entry_points.txt +2 -0
  261. dvt_core-1.11.0b4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,464 @@
1
+ """
2
+ Extended profile configuration for DVT unified profiles.
3
+
4
+ This module extends dbt's profile system to support:
5
+ - Multiple named profiles in one file (not just outputs)
6
+ - Profile references in sources
7
+ - Backward compatibility with dbt profiles
8
+ """
9
+
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ import yaml
15
+
16
+ from dbt_common.events.functions import fire_event
17
+ from dbt_common.events.types import Note
18
+ from dbt_common.exceptions import DbtRuntimeError
19
+
20
+
21
+ @dataclass
22
+ class ProfileReference:
23
+ """
24
+ Reference to a named profile.
25
+
26
+ This represents a connection configuration that can be used
27
+ as either a source or a target.
28
+ """
29
+
30
+ name: str
31
+ adapter: str
32
+ credentials: Dict[str, Any]
33
+ threads: int = 4
34
+
35
+ @classmethod
36
+ def from_dict(cls, name: str, data: Dict[str, Any]) -> "ProfileReference":
37
+ """
38
+ Create ProfileReference from dictionary.
39
+
40
+ Args:
41
+ name: Profile name
42
+ data: Profile configuration dictionary
43
+
44
+ Returns:
45
+ ProfileReference instance
46
+ """
47
+ # Extract adapter type (could be 'adapter' or 'type' for dbt compat)
48
+ adapter = data.get("adapter") or data.get("type")
49
+ if not adapter:
50
+ raise DbtRuntimeError(f"Profile '{name}' missing 'adapter' or 'type' field")
51
+
52
+ # Extract threads
53
+ threads = data.get("threads", 4)
54
+
55
+ # Everything else is credentials
56
+ credentials = {k: v for k, v in data.items() if k not in ("adapter", "type", "threads")}
57
+
58
+ return cls(
59
+ name=name,
60
+ adapter=adapter,
61
+ credentials=credentials,
62
+ threads=threads,
63
+ )
64
+
65
+ def to_connection_dict(self) -> Dict[str, Any]:
66
+ """
67
+ Convert to connection dictionary for adapter.
68
+
69
+ Returns:
70
+ Dictionary with adapter type and credentials
71
+ """
72
+ return {
73
+ "type": self.adapter,
74
+ "threads": self.threads,
75
+ **self.credentials,
76
+ }
77
+
78
+
79
+ class UnifiedProfileConfig:
80
+ """
81
+ Unified profile configuration supporting both DVT and dbt formats.
82
+
83
+ DVT format (unified profiles):
84
+ postgres_prod:
85
+ adapter: postgres
86
+ host: localhost
87
+ port: 5432
88
+ user: myuser
89
+ # ... more credentials
90
+
91
+ mysql_legacy:
92
+ adapter: mysql
93
+ host: legacy-db
94
+ # ... more credentials
95
+
96
+ dbt format (backward compatible):
97
+ my_project:
98
+ target: dev
99
+ outputs:
100
+ dev:
101
+ type: postgres
102
+ host: localhost
103
+ # ... more credentials
104
+ """
105
+
106
+ def __init__(self, profiles: Dict[str, ProfileReference], dbt_profiles: Dict[str, Any]):
107
+ """
108
+ Initialize UnifiedProfileConfig.
109
+
110
+ Args:
111
+ profiles: Named profiles dictionary
112
+ dbt_profiles: dbt-style profiles (for backward compat)
113
+ """
114
+ self.profiles = profiles
115
+ self.dbt_profiles = dbt_profiles
116
+
117
+ def get_profile(self, name: str) -> Optional[ProfileReference]:
118
+ """
119
+ Get profile by name.
120
+
121
+ Args:
122
+ name: Profile name
123
+
124
+ Returns:
125
+ ProfileReference or None if not found
126
+ """
127
+ return self.profiles.get(name)
128
+
129
+ def get_dbt_profile(self, name: str) -> Optional[Any]:
130
+ """
131
+ Get dbt-style profile by name.
132
+
133
+ Args:
134
+ name: Profile name
135
+
136
+ Returns:
137
+ DbtProfile dict or None if not found
138
+ """
139
+ return self.dbt_profiles.get(name)
140
+
141
+ def has_profile(self, name: str) -> bool:
142
+ """Check if profile exists."""
143
+ return name in self.profiles or name in self.dbt_profiles
144
+
145
+ def list_profiles(self) -> list[str]:
146
+ """List all available profile names."""
147
+ return sorted(set(list(self.profiles.keys()) + list(self.dbt_profiles.keys())))
148
+
149
+ @classmethod
150
+ def from_dict(cls, data: Dict[str, Any]) -> "UnifiedProfileConfig":
151
+ """
152
+ Parse profiles from dictionary.
153
+
154
+ Supports both DVT unified format and dbt format.
155
+
156
+ Args:
157
+ data: Parsed YAML dictionary
158
+
159
+ Returns:
160
+ UnifiedProfileConfig instance
161
+ """
162
+ profiles: Dict[str, ProfileReference] = {}
163
+ dbt_profiles: Dict[str, DbtProfile] = {}
164
+
165
+ for name, config in data.items():
166
+ if isinstance(config, dict):
167
+ # Check if this is dbt format (has 'target' and 'outputs')
168
+ if "target" in config and "outputs" in config:
169
+ # This is a dbt-style profile - skip for now
170
+ # We'll handle these with dbt's Profile class
171
+ dbt_profiles[name] = config # Store raw config for now
172
+ else:
173
+ # Check if this has 'adapter' or 'type' field (unified format)
174
+ if "adapter" in config or "type" in config:
175
+ try:
176
+ profiles[name] = ProfileReference.from_dict(name, config)
177
+ except Exception as e:
178
+ fire_event(Note(msg=f"Failed to parse profile '{name}': {e}"))
179
+ else:
180
+ # Could be a nested profile reference or other structure
181
+ # Check if it has a 'profile' key (reference to another profile)
182
+ if "profile" in config:
183
+ # This is a reference - handle in dbt compat layer
184
+ pass
185
+
186
+ return cls(profiles=profiles, dbt_profiles=dbt_profiles)
187
+
188
+ @classmethod
189
+ def load_from_file(cls, file_path: Path) -> "UnifiedProfileConfig":
190
+ """
191
+ Load profiles from YAML file.
192
+
193
+ Args:
194
+ file_path: Path to profiles.yml
195
+
196
+ Returns:
197
+ UnifiedProfileConfig instance
198
+
199
+ Raises:
200
+ DbtRuntimeError: If file cannot be read or parsed
201
+ """
202
+ try:
203
+ if not file_path.exists():
204
+ fire_event(Note(msg=f"Profiles file not found at {file_path}"))
205
+ return cls(profiles={}, dbt_profiles={})
206
+
207
+ with open(file_path, "r") as f:
208
+ data = yaml.safe_load(f)
209
+
210
+ if data is None:
211
+ fire_event(Note(msg=f"Empty profiles file at {file_path}"))
212
+ return cls(profiles={}, dbt_profiles={})
213
+
214
+ return cls.from_dict(data)
215
+
216
+ except yaml.YAMLError as e:
217
+ raise DbtRuntimeError(f"Failed to parse profiles: {e}")
218
+ except Exception as e:
219
+ raise DbtRuntimeError(f"Failed to load profiles from {file_path}: {e}")
220
+
221
+
222
+ def load_unified_profiles(project_dir: Optional[Path] = None) -> UnifiedProfileConfig:
223
+ """
224
+ Load unified profiles from standard locations.
225
+
226
+ Searches in order:
227
+ 1. <project_root>/profiles.yml
228
+ 2. ~/.dbt/profiles.yml
229
+ 3. Empty configuration
230
+
231
+ Args:
232
+ project_dir: Project directory (optional)
233
+
234
+ Returns:
235
+ UnifiedProfileConfig instance
236
+ """
237
+ # Try project directory first
238
+ if project_dir:
239
+ project_profiles = project_dir / "profiles.yml"
240
+ if project_profiles.exists():
241
+ fire_event(Note(msg=f"Loading profiles from {project_profiles}"))
242
+ return UnifiedProfileConfig.load_from_file(project_profiles)
243
+
244
+ # Try home directory (standard dbt location)
245
+ home_profiles = Path.home() / ".dbt" / "profiles.yml"
246
+ if home_profiles.exists():
247
+ fire_event(Note(msg=f"Loading profiles from {home_profiles}"))
248
+ return UnifiedProfileConfig.load_from_file(home_profiles)
249
+
250
+ # Empty configuration
251
+ fire_event(Note(msg="No profiles.yml found"))
252
+ return UnifiedProfileConfig(profiles={}, dbt_profiles={})
253
+
254
+
255
+ def resolve_profile_reference(
256
+ profile_name: str,
257
+ unified_profiles: UnifiedProfileConfig,
258
+ ) -> Optional[Dict[str, Any]]:
259
+ """
260
+ Resolve a profile reference to connection configuration.
261
+
262
+ Args:
263
+ profile_name: Name of profile to resolve
264
+ unified_profiles: Unified profile configuration
265
+
266
+ Returns:
267
+ Connection dictionary or None if not found
268
+ """
269
+ # Try unified profile first
270
+ profile = unified_profiles.get_profile(profile_name)
271
+ if profile:
272
+ return profile.to_connection_dict()
273
+
274
+ # Try dbt profile
275
+ dbt_profile = unified_profiles.get_dbt_profile(profile_name)
276
+ if dbt_profile:
277
+ # Return raw config - will be processed by dbt's Profile class
278
+ return dbt_profile
279
+
280
+ return None
281
+
282
+
283
+ class ProfileRegistry:
284
+ """
285
+ Registry for managing profile instances during execution.
286
+
287
+ This keeps track of all profiles referenced by sources and models,
288
+ and ensures they're properly initialized for use by compute engines.
289
+
290
+ Usage:
291
+ # In CLI commands
292
+ registry = ProfileRegistry(unified_profiles)
293
+ profile = registry.get_or_create_profile("postgres_prod")
294
+
295
+ # Test all profiles
296
+ results = registry.test_all_profiles()
297
+
298
+ # Get adapter for a profile
299
+ adapter = registry.get_adapter("postgres_prod", mp_context)
300
+ """
301
+
302
+ def __init__(self, unified_profiles: UnifiedProfileConfig):
303
+ """
304
+ Initialize ProfileRegistry.
305
+
306
+ Args:
307
+ unified_profiles: Unified profile configuration
308
+ """
309
+ self.unified_profiles = unified_profiles
310
+ self._initialized_profiles: Dict[str, Any] = {}
311
+ self._adapters: Dict[str, Any] = {}
312
+
313
+ def get_or_create_profile(self, profile_name: str) -> Optional[Any]:
314
+ """
315
+ Get or create profile instance.
316
+
317
+ Args:
318
+ profile_name: Profile name
319
+
320
+ Returns:
321
+ Profile instance or None if not found
322
+ """
323
+ # Check cache
324
+ if profile_name in self._initialized_profiles:
325
+ return self._initialized_profiles[profile_name]
326
+
327
+ # Resolve profile
328
+ profile_config = resolve_profile_reference(profile_name, self.unified_profiles)
329
+ if not profile_config:
330
+ return None
331
+
332
+ # Create profile instance
333
+ # For now, store the config. Full adapter integration happens in get_adapter()
334
+ self._initialized_profiles[profile_name] = profile_config
335
+
336
+ return profile_config
337
+
338
+ def get_adapter(self, profile_name: str, mp_context: Optional[Any] = None) -> Optional[Any]:
339
+ """
340
+ Get adapter instance for a profile.
341
+
342
+ This creates and caches adapter instances for profiles.
343
+ Requires multiprocessing context for adapter initialization.
344
+
345
+ Args:
346
+ profile_name: Profile name
347
+ mp_context: Multiprocessing context (required for adapter creation)
348
+
349
+ Returns:
350
+ Adapter instance or None if profile not found
351
+ """
352
+ # Check cache
353
+ if profile_name in self._adapters:
354
+ return self._adapters[profile_name]
355
+
356
+ # Get profile config
357
+ profile = self.get_or_create_profile(profile_name)
358
+ if not profile:
359
+ return None
360
+
361
+ # Need mp_context to create adapters
362
+ if not mp_context:
363
+ raise DbtRuntimeError(
364
+ "mp_context required to create adapter instances. "
365
+ "Use get_or_create_profile() for config-only access."
366
+ )
367
+
368
+ # Create adapter using MultiAdapterManager
369
+ from dvt.adapters.multi_adapter_manager import MultiAdapterManager
370
+
371
+ manager = MultiAdapterManager(self.unified_profiles, mp_context)
372
+ adapter = manager.get_or_create_adapter(profile_name)
373
+ self._adapters[profile_name] = adapter
374
+
375
+ return adapter
376
+
377
+ def list_all_profiles(self) -> list[str]:
378
+ """
379
+ List all available profile names.
380
+
381
+ Returns:
382
+ List of profile names from unified profiles
383
+ """
384
+ return self.unified_profiles.list_profiles()
385
+
386
+ def list_initialized_profiles(self) -> list[str]:
387
+ """
388
+ List profile names that have been initialized.
389
+
390
+ Returns:
391
+ List of cached profile names
392
+ """
393
+ return list(self._initialized_profiles.keys())
394
+
395
+ def test_profile(self, profile_name: str, mp_context: Optional[Any] = None) -> Dict[str, Any]:
396
+ """
397
+ Test a single profile connection.
398
+
399
+ Args:
400
+ profile_name: Profile name to test
401
+ mp_context: Multiprocessing context for adapter initialization
402
+
403
+ Returns:
404
+ Test result dictionary with 'success', 'message', and optional 'error' keys
405
+ """
406
+ try:
407
+ # Get profile config
408
+ profile = self.get_or_create_profile(profile_name)
409
+ if not profile:
410
+ return {
411
+ "success": False,
412
+ "profile": profile_name,
413
+ "message": f"Profile '{profile_name}' not found",
414
+ }
415
+
416
+ # If mp_context provided, test actual connection
417
+ if mp_context:
418
+ adapter = self.get_adapter(profile_name, mp_context)
419
+ if adapter:
420
+ # Try to connect
421
+ # Note: Different adapters have different connection methods
422
+ # For now, just verify adapter creation succeeded
423
+ return {
424
+ "success": True,
425
+ "profile": profile_name,
426
+ "adapter_type": getattr(profile, "adapter", "unknown"),
427
+ "message": "Profile configuration valid and adapter created",
428
+ }
429
+
430
+ # Without mp_context, just verify config is loadable
431
+ return {
432
+ "success": True,
433
+ "profile": profile_name,
434
+ "adapter_type": getattr(profile, "adapter", "unknown"),
435
+ "message": "Profile configuration valid",
436
+ }
437
+
438
+ except Exception as e:
439
+ return {
440
+ "success": False,
441
+ "profile": profile_name,
442
+ "message": f"Profile test failed: {str(e)}",
443
+ "error": str(e),
444
+ }
445
+
446
+ def test_all_profiles(self, mp_context: Optional[Any] = None) -> Dict[str, Dict[str, Any]]:
447
+ """
448
+ Test all available profiles.
449
+
450
+ Args:
451
+ mp_context: Multiprocessing context for adapter initialization
452
+
453
+ Returns:
454
+ Dictionary mapping profile names to test results
455
+ """
456
+ results = {}
457
+ for profile_name in self.list_all_profiles():
458
+ results[profile_name] = self.test_profile(profile_name, mp_context)
459
+ return results
460
+
461
+ def clear_cache(self) -> None:
462
+ """Clear profile and adapter instance caches."""
463
+ self._initialized_profiles.clear()
464
+ self._adapters.clear()