fal 0.9.2__py3-none-any.whl → 0.9.4__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 fal might be problematic. Click here for more details.

Files changed (93) hide show
  1. _fal_testing/utils.py +2 -2
  2. dbt/adapters/fal/__init__.py +21 -0
  3. dbt/adapters/fal/__version__.py +1 -0
  4. dbt/adapters/fal/connections.py +18 -0
  5. dbt/adapters/fal/impl.py +93 -0
  6. dbt/adapters/fal/load_db_profile.py +80 -0
  7. dbt/adapters/fal/wrappers.py +113 -0
  8. dbt/adapters/fal_experimental/__init__.py +11 -0
  9. dbt/adapters/fal_experimental/__version__.py +1 -0
  10. dbt/adapters/fal_experimental/adapter.py +149 -0
  11. dbt/adapters/fal_experimental/adapter_support.py +234 -0
  12. dbt/adapters/fal_experimental/connections.py +72 -0
  13. dbt/adapters/fal_experimental/impl.py +240 -0
  14. dbt/adapters/fal_experimental/support/athena.py +92 -0
  15. dbt/adapters/fal_experimental/support/bigquery.py +74 -0
  16. dbt/adapters/fal_experimental/support/duckdb.py +28 -0
  17. dbt/adapters/fal_experimental/support/postgres.py +88 -0
  18. dbt/adapters/fal_experimental/support/redshift.py +56 -0
  19. dbt/adapters/fal_experimental/support/snowflake.py +76 -0
  20. dbt/adapters/fal_experimental/support/trino.py +26 -0
  21. dbt/adapters/fal_experimental/telemetry/__init__.py +1 -0
  22. dbt/adapters/fal_experimental/telemetry/telemetry.py +411 -0
  23. dbt/adapters/fal_experimental/teleport.py +192 -0
  24. dbt/adapters/fal_experimental/teleport_adapter_support.py +23 -0
  25. dbt/adapters/fal_experimental/teleport_support/duckdb.py +122 -0
  26. dbt/adapters/fal_experimental/teleport_support/snowflake.py +72 -0
  27. dbt/adapters/fal_experimental/utils/__init__.py +50 -0
  28. dbt/adapters/fal_experimental/utils/environments.py +302 -0
  29. dbt/fal/adapters/python/__init__.py +3 -0
  30. dbt/fal/adapters/python/connections.py +319 -0
  31. dbt/fal/adapters/python/impl.py +291 -0
  32. dbt/fal/adapters/teleport/__init__.py +3 -0
  33. dbt/fal/adapters/teleport/impl.py +103 -0
  34. dbt/fal/adapters/teleport/info.py +73 -0
  35. dbt/include/fal/__init__.py +3 -0
  36. dbt/include/fal/dbt_project.yml +5 -0
  37. dbt/include/fal/macros/materializations/table.sql +46 -0
  38. dbt/include/fal/macros/teleport_duckdb.sql +8 -0
  39. dbt/include/fal/macros/teleport_snowflake.sql +31 -0
  40. dbt/include/fal_experimental/__init__.py +3 -0
  41. dbt/include/fal_experimental/dbt_project.yml +5 -0
  42. dbt/include/fal_experimental/macros/materializations/table.sql +36 -0
  43. fal/__init__.py +61 -11
  44. fal/dbt/__init__.py +11 -0
  45. fal/dbt/cli/__init__.py +1 -0
  46. fal/{cli → dbt/cli}/args.py +7 -2
  47. fal/{cli → dbt/cli}/cli.py +18 -3
  48. fal/{cli → dbt/cli}/dbt_runner.py +1 -1
  49. fal/{cli → dbt/cli}/fal_runner.py +6 -6
  50. fal/{cli → dbt/cli}/flow_runner.py +9 -9
  51. fal/{cli → dbt/cli}/model_generator/model_generator.py +5 -5
  52. fal/{cli → dbt/cli}/selectors.py +2 -2
  53. fal/{fal_script.py → dbt/fal_script.py} +4 -4
  54. {faldbt → fal/dbt/integration}/lib.py +2 -2
  55. {faldbt → fal/dbt/integration}/magics.py +2 -2
  56. {faldbt → fal/dbt/integration}/parse.py +7 -7
  57. {faldbt → fal/dbt/integration}/project.py +7 -7
  58. fal/dbt/integration/utils/yaml_helper.py +80 -0
  59. fal/dbt/new/project.py +43 -0
  60. fal/{node_graph.py → dbt/node_graph.py} +2 -2
  61. fal/{packages → dbt/packages}/dependency_analysis.py +32 -38
  62. fal/{packages → dbt/packages}/environments/__init__.py +3 -3
  63. fal/{packages → dbt/packages}/environments/base.py +2 -2
  64. fal/{packages → dbt/packages}/environments/conda.py +3 -3
  65. fal/{packages → dbt/packages}/environments/virtual_env.py +3 -3
  66. fal/{packages → dbt/packages}/isolated_runner.py +5 -5
  67. fal/{planner → dbt/planner}/executor.py +4 -4
  68. fal/{planner → dbt/planner}/plan.py +3 -3
  69. fal/{planner → dbt/planner}/schedule.py +5 -5
  70. fal/{planner → dbt/planner}/tasks.py +5 -5
  71. fal/{telemetry → dbt/telemetry}/telemetry.py +4 -4
  72. fal/{typing.py → dbt/typing.py} +2 -2
  73. fal/{utils.py → dbt/utils.py} +2 -2
  74. {fal-0.9.2.dist-info → fal-0.9.4.dist-info}/METADATA +98 -117
  75. fal-0.9.4.dist-info/RECORD +91 -0
  76. fal-0.9.4.dist-info/entry_points.txt +4 -0
  77. fal/cli/__init__.py +0 -1
  78. fal-0.9.2.dist-info/RECORD +0 -47
  79. fal-0.9.2.dist-info/entry_points.txt +0 -3
  80. {faldbt → dbt/adapters/fal_experimental}/utils/yaml_helper.py +0 -0
  81. /fal/{cli → dbt/cli}/model_generator/__init__.py +0 -0
  82. /fal/{cli → dbt/cli}/model_generator/module_check.py +0 -0
  83. /fal/{feature_store → dbt/feature_store}/__init__.py +0 -0
  84. /fal/{feature_store → dbt/feature_store}/feature.py +0 -0
  85. /fal/{packages → dbt/integration}/__init__.py +0 -0
  86. {faldbt → fal/dbt/integration}/logger.py +0 -0
  87. /fal/{planner → dbt/integration/utils}/__init__.py +0 -0
  88. {faldbt → fal/dbt/integration}/version.py +0 -0
  89. /fal/{telemetry → dbt/packages}/__init__.py +0 -0
  90. /fal/{packages → dbt/packages}/bridge.py +0 -0
  91. {faldbt → fal/dbt/planner}/__init__.py +0 -0
  92. {faldbt/utils → fal/dbt/telemetry}/__init__.py +0 -0
  93. {fal-0.9.2.dist-info → fal-0.9.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,319 @@
1
+ import abc
2
+ import os
3
+ from time import sleep
4
+ import sys
5
+ from multiprocessing.synchronize import RLock
6
+ from threading import get_ident
7
+ from typing import (
8
+ Any,
9
+ Dict,
10
+ Tuple,
11
+ Hashable,
12
+ Optional,
13
+ List,
14
+ Type,
15
+ Union,
16
+ Iterable,
17
+ Callable,
18
+ )
19
+
20
+ from dbt.exceptions import (
21
+ NotImplementedError,
22
+ InvalidConnectionError,
23
+ DbtInternalError,
24
+ CompilationError,
25
+ FailedToConnectError,
26
+ )
27
+ from dbt.contracts.connection import (
28
+ Connection,
29
+ Identifier,
30
+ ConnectionState,
31
+ AdapterRequiredConfig,
32
+ LazyHandle,
33
+ AdapterResponse,
34
+ )
35
+ from dbt.events import AdapterLogger
36
+ from dbt.events.functions import fire_event
37
+ from dbt.events.types import (
38
+ NewConnection,
39
+ ConnectionReused,
40
+ ConnectionLeftOpen,
41
+ ConnectionClosed,
42
+ )
43
+ from dbt import flags
44
+
45
+ SleepTime = Union[int, float] # As taken by time.sleep.
46
+ AdapterHandle = Any # Adapter connection handle objects can be any class.
47
+
48
+
49
+ class PythonConnectionManager(metaclass=abc.ABCMeta):
50
+ """Methods to implement:
51
+ - open
52
+ - execute
53
+
54
+ You must also set the 'TYPE' class attribute with a class-unique constant
55
+ string.
56
+ """
57
+
58
+ TYPE: str = NotImplemented
59
+
60
+ def __init__(self, profile: AdapterRequiredConfig):
61
+ self.profile = profile
62
+ if profile.credentials.type == self.TYPE:
63
+ self.credentials = profile.credentials
64
+ else:
65
+ self.credentials = profile.python_adapter_credentials
66
+
67
+ self.thread_connections: Dict[Hashable, Connection] = {}
68
+ self.lock: RLock = flags.MP_CONTEXT.RLock()
69
+
70
+ @staticmethod
71
+ def get_thread_identifier() -> Hashable:
72
+ # note that get_ident() may be re-used, but we should never experience
73
+ # that within a single process
74
+ return (os.getpid(), get_ident())
75
+
76
+ def get_thread_connection(self) -> Connection:
77
+ key = self.get_thread_identifier()
78
+ with self.lock:
79
+ if key not in self.thread_connections:
80
+ raise InvalidConnectionError(key, list(self.thread_connections))
81
+ return self.thread_connections[key]
82
+
83
+ def set_thread_connection(self, conn: Connection) -> None:
84
+ key = self.get_thread_identifier()
85
+ if key in self.thread_connections:
86
+ raise DbtInternalError(
87
+ "In set_thread_connection, existing connection exists for {}"
88
+ )
89
+ self.thread_connections[key] = conn
90
+
91
+ def get_if_exists(self) -> Optional[Connection]:
92
+ key = self.get_thread_identifier()
93
+ with self.lock:
94
+ return self.thread_connections.get(key)
95
+
96
+ def clear_thread_connection(self) -> None:
97
+ key = self.get_thread_identifier()
98
+ with self.lock:
99
+ if key in self.thread_connections:
100
+ del self.thread_connections[key]
101
+
102
+ def set_connection_name(self, name: Optional[str] = None) -> Connection:
103
+ conn_name: str
104
+ if name is None:
105
+ # if a name isn't specified, we'll re-use a single handle
106
+ # named 'master'
107
+ conn_name = "master"
108
+ else:
109
+ if not isinstance(name, str):
110
+ raise CompilationError(
111
+ f"For connection name, got {name} - not a string!"
112
+ )
113
+ assert isinstance(name, str)
114
+ conn_name = name
115
+
116
+ conn = self.get_if_exists()
117
+ if conn is None:
118
+ conn = Connection(
119
+ type=Identifier(self.TYPE),
120
+ name=None,
121
+ state=ConnectionState.INIT,
122
+ transaction_open=False,
123
+ handle=None,
124
+ credentials=self.credentials,
125
+ )
126
+ self.set_thread_connection(conn)
127
+
128
+ if conn.name == conn_name and conn.state == "open":
129
+ return conn
130
+
131
+ fire_event(NewConnection(conn_name=conn_name, conn_type=self.TYPE))
132
+
133
+ if conn.state == "open":
134
+ fire_event(ConnectionReused(conn_name=conn_name))
135
+ else:
136
+ conn.handle = LazyHandle(self.open)
137
+
138
+ conn.name = conn_name
139
+ return conn
140
+
141
+ @classmethod
142
+ def retry_connection(
143
+ cls,
144
+ connection: Connection,
145
+ connect: Callable[[], AdapterHandle],
146
+ logger: AdapterLogger,
147
+ retryable_exceptions: Iterable[Type[Exception]],
148
+ retry_limit: int = 1,
149
+ retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1,
150
+ _attempts: int = 0,
151
+ ) -> Connection:
152
+ """Given a Connection, set its handle by calling connect.
153
+
154
+ The calls to connect will be retried up to retry_limit times to deal with transient
155
+ connection errors. By default, one retry will be attempted if retryable_exceptions is set.
156
+
157
+ :param Connection connection: An instance of a Connection that needs a handle to be set,
158
+ usually when attempting to open it.
159
+ :param connect: A callable that returns the appropiate connection handle for a
160
+ given adapter. This callable will be retried retry_limit times if a subclass of any
161
+ Exception in retryable_exceptions is raised by connect.
162
+ :type connect: Callable[[], AdapterHandle]
163
+ :param AdapterLogger logger: A logger to emit messages on retry attempts or errors. When
164
+ handling expected errors, we call debug, and call warning on unexpected errors or when
165
+ all retry attempts have been exhausted.
166
+ :param retryable_exceptions: An iterable of exception classes that if raised by
167
+ connect should trigger a retry.
168
+ :type retryable_exceptions: Iterable[Type[Exception]]
169
+ :param int retry_limit: How many times to retry the call to connect. If this limit
170
+ is exceeded before a successful call, a FailedToConnectError will be raised.
171
+ Must be non-negative.
172
+ :param retry_timeout: Time to wait between attempts to connect. Can also take a
173
+ Callable that takes the number of attempts so far, beginning at 0, and returns an int
174
+ or float to be passed to time.sleep.
175
+ :type retry_timeout: Union[Callable[[int], SleepTime], SleepTime] = 1
176
+ :param int _attempts: Parameter used to keep track of the number of attempts in calling the
177
+ connect function across recursive calls. Passed as an argument to retry_timeout if it
178
+ is a Callable. This parameter should not be set by the initial caller.
179
+ :raises dbt.exceptions.FailedToConnectError: Upon exhausting all retry attempts without
180
+ successfully acquiring a handle.
181
+ :return: The given connection with its appropriate state and handle attributes set
182
+ depending on whether we successfully acquired a handle or not.
183
+ """
184
+ timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
185
+ if timeout < 0:
186
+ raise FailedToConnectError(
187
+ "retry_timeout cannot be negative or return a negative time."
188
+ )
189
+
190
+ if retry_limit < 0 or retry_limit > sys.getrecursionlimit():
191
+ # This guard is not perfect others may add to the recursion limit (e.g. built-ins).
192
+ connection.handle = None
193
+ connection.state = ConnectionState.FAIL
194
+ raise FailedToConnectError("retry_limit cannot be negative")
195
+
196
+ try:
197
+ connection.handle = connect()
198
+ connection.state = ConnectionState.OPEN
199
+ return connection
200
+
201
+ except tuple(retryable_exceptions) as e:
202
+ if retry_limit <= 0:
203
+ connection.handle = None
204
+ connection.state = ConnectionState.FAIL
205
+ raise FailedToConnectError(str(e))
206
+
207
+ logger.debug(
208
+ f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
209
+ f"{retry_limit} attempts remaining. Retrying in {timeout} seconds.\n"
210
+ f"Error:\n{e}"
211
+ )
212
+
213
+ sleep(timeout)
214
+ return cls.retry_connection(
215
+ connection=connection,
216
+ connect=connect,
217
+ logger=logger,
218
+ retry_limit=retry_limit - 1,
219
+ retry_timeout=retry_timeout,
220
+ retryable_exceptions=retryable_exceptions,
221
+ _attempts=_attempts + 1,
222
+ )
223
+
224
+ except Exception as e:
225
+ connection.handle = None
226
+ connection.state = ConnectionState.FAIL
227
+ raise FailedToConnectError(str(e))
228
+
229
+ @abc.abstractmethod
230
+ def cancel(self, connection: Connection):
231
+ """Cancel the given connection."""
232
+ raise NotImplementedError("`cancel` is not implemented for this adapter!")
233
+
234
+ def cancel_open(self) -> List[str]:
235
+ names = []
236
+ this_connection = self.get_if_exists()
237
+ with self.lock:
238
+ for connection in self.thread_connections.values():
239
+ if connection is this_connection:
240
+ continue
241
+
242
+ # if the connection failed, the handle will be None so we have
243
+ # nothing to cancel.
244
+ if (
245
+ connection.handle is not None
246
+ and connection.state == ConnectionState.OPEN
247
+ ):
248
+ self.cancel(connection)
249
+ if connection.name is not None:
250
+ names.append(connection.name)
251
+ return names
252
+
253
+ @classmethod
254
+ @abc.abstractmethod
255
+ def open(cls, connection: Connection) -> Connection:
256
+ """Open the given connection on the adapter and return it.
257
+
258
+ This may mutate the given connection (in particular, its state and its
259
+ handle).
260
+
261
+ This should be thread-safe, or hold the lock if necessary. The given
262
+ connection should not be in either in_use or available.
263
+ """
264
+ raise NotImplementedError("`open` is not implemented for this adapter!")
265
+
266
+ def release(self) -> None:
267
+ with self.lock:
268
+ conn = self.get_if_exists()
269
+ if conn is None:
270
+ return
271
+
272
+ try:
273
+ # always close the connection
274
+ self.close(conn)
275
+ except Exception:
276
+ # if rollback or close failed, remove our busted connection
277
+ self.clear_thread_connection()
278
+ raise
279
+
280
+ def cleanup_all(self) -> None:
281
+ with self.lock:
282
+ for connection in self.thread_connections.values():
283
+ if connection.state not in {"closed", "init"}:
284
+ fire_event(ConnectionLeftOpen(conn_name=connection.name))
285
+ else:
286
+ fire_event(ConnectionClosed(conn_name=connection.name))
287
+ self.close(connection)
288
+
289
+ # garbage collect these connections
290
+ self.thread_connections.clear()
291
+
292
+ @classmethod
293
+ def _close_handle(cls, connection: Connection) -> None:
294
+ """Perform the actual close operation."""
295
+ # On windows, sometimes connection handles don't have a close() attr.
296
+ if hasattr(connection.handle, "close"):
297
+ connection.handle.close()
298
+
299
+ @classmethod
300
+ def close(cls, connection: Connection) -> Connection:
301
+ # if the connection is in closed or init, there's nothing to do
302
+ if connection.state in {ConnectionState.CLOSED, ConnectionState.INIT}:
303
+ return connection
304
+
305
+ cls._close_handle(connection)
306
+ connection.state = ConnectionState.CLOSED
307
+
308
+ return connection
309
+
310
+ @abc.abstractmethod
311
+ def execute(self, compiled_code: str) -> Tuple[AdapterResponse, Any]:
312
+ """Execute the given Python code.
313
+
314
+ :param str compiled_code: The Python code to execute.
315
+ :return: A tuple of the run status and results
316
+ (type is left to be decided by the Adapter implementation for now).
317
+ :rtype: Tuple[AdapterResponse, Any]
318
+ """
319
+ raise NotImplementedError("`execute` is not implemented for this adapter!")
@@ -0,0 +1,291 @@
1
+ import abc
2
+ from contextlib import contextmanager
3
+ import time
4
+ from typing import (
5
+ Optional,
6
+ Type,
7
+ Dict,
8
+ Any,
9
+ Mapping,
10
+ Iterator,
11
+ )
12
+
13
+ from dbt.adapters.factory import get_adapter
14
+ from dbt.exceptions import (
15
+ NotImplementedError,
16
+ DbtRuntimeError,
17
+ )
18
+ from dbt.contracts.graph.nodes import ResultNode
19
+ from dbt.adapters.protocol import AdapterConfig, ConnectionManagerProtocol
20
+ from dbt.events.functions import fire_event
21
+ from dbt.events.types import (
22
+ CodeExecution,
23
+ CodeExecutionStatus,
24
+ )
25
+ from dbt.adapters.base.meta import AdapterMeta, available
26
+ from dbt.contracts.connection import Credentials, Connection, AdapterResponse
27
+
28
+
29
+ def log_code_execution(code_execution_function):
30
+ # decorator to log code and execution time
31
+ if code_execution_function.__name__ != "submit_python_job":
32
+ raise ValueError("this should be only used to log submit_python_job now")
33
+
34
+ def execution_with_log(*args):
35
+ self = args[0]
36
+ connection_name = self.connections.get_thread_connection().name
37
+ fire_event(CodeExecution(conn_name=connection_name, code_content=args[2]))
38
+ start_time = time.time()
39
+ response = code_execution_function(*args)
40
+ fire_event(
41
+ CodeExecutionStatus(
42
+ status=response._message, elapsed=round((time.time() - start_time), 2)
43
+ )
44
+ )
45
+ return response
46
+
47
+ return execution_with_log
48
+
49
+
50
+ class PythonJobHelper:
51
+ def __init__(self, parsed_model: Dict, credential: Credentials) -> None:
52
+ raise NotImplementedError("PythonJobHelper is not implemented yet")
53
+
54
+ def submit(self, compiled_code: str) -> Any:
55
+ raise NotImplementedError(
56
+ "PythonJobHelper submit function is not implemented yet"
57
+ )
58
+
59
+
60
+ class PythonAdapter(metaclass=AdapterMeta):
61
+ """The PythonAdapter provides an abstract base class for Python adapters.
62
+
63
+ Adapters must implement the following methods and macros. Some of the
64
+ methods can be safely overridden as a noop, where it makes sense. Those
65
+ methods are marked with a (passable) in their docstrings. Check docstrings
66
+ for type information, etc.
67
+
68
+ To implement a macro, implement "${adapter_type}__${macro_name}" in the
69
+ adapter's internal project.
70
+
71
+ To invoke a method in an adapter macro, call it on the 'adapter' Jinja
72
+ object using dot syntax.
73
+
74
+ To invoke a method in model code, add the @available decorator atop a method
75
+ declaration. Methods are invoked as macros.
76
+
77
+ Methods:
78
+ - is_cancelable
79
+
80
+ - python_submission_helpers
81
+ - default_python_submission_method
82
+ - generate_python_submission_response
83
+ or
84
+ - submit_python_job
85
+
86
+ Macros:
87
+
88
+ """
89
+
90
+ ConnectionManager: Type[ConnectionManagerProtocol]
91
+
92
+ # A set of clobber config fields accepted by this adapter
93
+ # for use in materializations
94
+ AdapterSpecificConfigs: Type[AdapterConfig] = AdapterConfig
95
+
96
+ def __init__(self, config):
97
+ self.config = config
98
+ self.connections = self.ConnectionManager(config)
99
+
100
+ # HACK: A Python adapter does not have _available_ all the attributes a DB adapter does.
101
+ # Since we use the DB adapter as the storage for the Python adapter, we must proxy to it
102
+ # all the unhandled calls.
103
+ #
104
+ # Another option is to write a PythonAdapter-specific DBWrapper (PythonWrapper?) that is
105
+ # aware of this case. This may be appealing because a _complete_ adapter (DB+Python) would
106
+ # then be more easily used to replace the Python part of any other adapter.
107
+
108
+ self._db_adapter = get_adapter(config)
109
+ self.Relation = self._db_adapter.Relation
110
+ self.Column = self._db_adapter.Column
111
+
112
+ def cache_added(self, *args, **kwargs):
113
+ return self._db_adapter.cache_added(*args, **kwargs)
114
+
115
+ @available.parse_none
116
+ def get_relation(self, *args, **kwargs):
117
+ return self._db_adapter.get_relation(*args, **kwargs)
118
+
119
+ @classmethod
120
+ def date_function(cls):
121
+ # HACK: to appease the ProviderContext
122
+ return """
123
+ import datetime
124
+ return datetime.datetime.now()
125
+ """
126
+
127
+ ###
128
+ # Methods that pass through to the connection manager
129
+ ###
130
+ def acquire_connection(self, name=None) -> Connection:
131
+ return self.connections.set_connection_name(name)
132
+
133
+ def release_connection(self) -> None:
134
+ self.connections.release()
135
+
136
+ def cleanup_connections(self) -> None:
137
+ self.connections.cleanup_all()
138
+
139
+ def nice_connection_name(self) -> str:
140
+ conn = self.connections.get_if_exists()
141
+ if conn is None or conn.name is None:
142
+ return "<None>"
143
+ return conn.name
144
+
145
+ @contextmanager
146
+ def connection_named(
147
+ self, name: str, node: Optional[ResultNode] = None
148
+ ) -> Iterator[None]:
149
+ try:
150
+ self.acquire_connection(name)
151
+ yield
152
+ finally:
153
+ self.release_connection()
154
+
155
+ @contextmanager
156
+ def connection_for(self, node: ResultNode) -> Iterator[None]:
157
+ with self.connection_named(node.unique_id, node):
158
+ yield
159
+
160
+ @classmethod
161
+ @abc.abstractmethod
162
+ def is_cancelable(cls) -> bool:
163
+ raise NotImplementedError(
164
+ "`is_cancelable` is not implemented for this adapter!"
165
+ )
166
+
167
+ ###
168
+ # Methods that should never be overridden
169
+ ###
170
+ @classmethod
171
+ def type(cls) -> str:
172
+ """Get the type of this adapter. Types must be class-unique and
173
+ consistent.
174
+
175
+ :return: The type name
176
+ :rtype: str
177
+ """
178
+ return cls.ConnectionManager.TYPE
179
+
180
+ ###
181
+ # ODBC FUNCTIONS -- these should not need to change for every adapter,
182
+ # although some adapters may override them
183
+ ###
184
+ def cancel_open_connections(self):
185
+ """Cancel all open connections."""
186
+ return self.connections.cancel_open()
187
+
188
+ def pre_model_hook(self, config: Mapping[str, Any]) -> Any:
189
+ """A hook for running some operation before the model materialization
190
+ runs. The hook can assume it has a connection available.
191
+
192
+ The only parameter is a configuration dictionary (the same one
193
+ available in the materialization context). It should be considered
194
+ read-only.
195
+
196
+ The pre-model hook may return anything as a context, which will be
197
+ passed to the post-model hook.
198
+ """
199
+ pass
200
+
201
+ def post_model_hook(self, config: Mapping[str, Any], context: Any) -> None:
202
+ """A hook for running some operation after the model materialization
203
+ runs. The hook can assume it has a connection available.
204
+
205
+ The first parameter is a configuration dictionary (the same one
206
+ available in the materialization context). It should be considered
207
+ read-only.
208
+
209
+ The second parameter is the value returned by pre_mdoel_hook.
210
+ """
211
+ pass
212
+
213
+ def get_compiler(self):
214
+ from dbt.compilation import Compiler
215
+
216
+ return Compiler(self.config)
217
+
218
+ ###
219
+ # Python
220
+ ###
221
+ @property
222
+ def python_submission_helpers(self) -> Dict[str, Type[PythonJobHelper]]:
223
+ raise NotImplementedError("python_submission_helpers is not specified")
224
+
225
+ @property
226
+ def default_python_submission_method(self) -> str:
227
+ raise NotImplementedError("default_python_submission_method is not specified")
228
+
229
+ @log_code_execution
230
+ def submit_python_job(
231
+ self, parsed_model: dict, compiled_code: str
232
+ ) -> AdapterResponse:
233
+ submission_method = parsed_model["config"].get(
234
+ "submission_method", self.default_python_submission_method
235
+ )
236
+ if submission_method not in self.python_submission_helpers:
237
+ raise NotImplementedError(
238
+ "Submission method {} is not supported for current adapter".format(
239
+ submission_method
240
+ )
241
+ )
242
+ job_helper = self.python_submission_helpers[submission_method](
243
+ parsed_model, self.connections.credentials
244
+ )
245
+ submission_result = job_helper.submit(compiled_code)
246
+ # process submission result to generate adapter response
247
+ return self.generate_python_submission_response(submission_result)
248
+
249
+ def generate_python_submission_response(
250
+ self, submission_result: Any
251
+ ) -> AdapterResponse:
252
+ raise NotImplementedError(
253
+ "Your adapter need to implement generate_python_submission_response"
254
+ )
255
+
256
+ def valid_incremental_strategies(self):
257
+ """The set of standard builtin strategies which this adapter supports out-of-the-box.
258
+ Not used to validate custom strategies defined by end users.
259
+ """
260
+ return ["append"]
261
+
262
+ def builtin_incremental_strategies(self):
263
+ return ["append", "delete+insert", "merge", "insert_overwrite"]
264
+
265
+ @available.parse_none
266
+ def get_incremental_strategy_macro(self, model_context, strategy: str):
267
+ # Construct macro_name from strategy name
268
+ if strategy is None:
269
+ strategy = "default"
270
+
271
+ # validate strategies for this adapter
272
+ valid_strategies = self.valid_incremental_strategies()
273
+ valid_strategies.append("default")
274
+ builtin_strategies = self.builtin_incremental_strategies()
275
+ if strategy in builtin_strategies and strategy not in valid_strategies:
276
+ raise DbtRuntimeError(
277
+ f"The incremental strategy '{strategy}' is not valid for this adapter"
278
+ )
279
+
280
+ strategy = strategy.replace("+", "_")
281
+ macro_name = f"get_incremental_{strategy}_sql"
282
+ # The model_context should have MacroGenerator callable objects for all macros
283
+ if macro_name not in model_context:
284
+ raise DbtRuntimeError(
285
+ 'dbt could not find an incremental strategy macro with the name "{}" in {}'.format(
286
+ macro_name, self.config.project_name
287
+ )
288
+ )
289
+
290
+ # This returns a callable macro
291
+ return model_context[macro_name]
@@ -0,0 +1,3 @@
1
+ # these are all just exports, #noqa them so flake8 will be happy
2
+ from .impl import TeleportAdapter # noqa
3
+ from .info import TeleportInfo, S3TeleportInfo, LocalTeleportInfo # noqa