pycarlo 0.12.24__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 pycarlo might be problematic. Click here for more details.

Files changed (48) hide show
  1. pycarlo/__init__.py +0 -0
  2. pycarlo/common/__init__.py +31 -0
  3. pycarlo/common/errors.py +31 -0
  4. pycarlo/common/files.py +78 -0
  5. pycarlo/common/http.py +36 -0
  6. pycarlo/common/mcon.py +26 -0
  7. pycarlo/common/retries.py +129 -0
  8. pycarlo/common/settings.py +89 -0
  9. pycarlo/common/utils.py +51 -0
  10. pycarlo/core/__init__.py +10 -0
  11. pycarlo/core/client.py +267 -0
  12. pycarlo/core/endpoint.py +289 -0
  13. pycarlo/core/operations.py +25 -0
  14. pycarlo/core/session.py +127 -0
  15. pycarlo/features/__init__.py +10 -0
  16. pycarlo/features/circuit_breakers/__init__.py +3 -0
  17. pycarlo/features/circuit_breakers/exceptions.py +10 -0
  18. pycarlo/features/circuit_breakers/service.py +346 -0
  19. pycarlo/features/dbt/__init__.py +3 -0
  20. pycarlo/features/dbt/dbt_importer.py +208 -0
  21. pycarlo/features/dbt/queries.py +31 -0
  22. pycarlo/features/exceptions.py +18 -0
  23. pycarlo/features/metadata/__init__.py +32 -0
  24. pycarlo/features/metadata/asset_allow_block_list.py +22 -0
  25. pycarlo/features/metadata/asset_filters_container.py +79 -0
  26. pycarlo/features/metadata/base_allow_block_list.py +137 -0
  27. pycarlo/features/metadata/metadata_allow_block_list.py +94 -0
  28. pycarlo/features/metadata/metadata_filters_container.py +262 -0
  29. pycarlo/features/pii/__init__.py +5 -0
  30. pycarlo/features/pii/constants.py +3 -0
  31. pycarlo/features/pii/pii_filterer.py +179 -0
  32. pycarlo/features/pii/queries.py +20 -0
  33. pycarlo/features/pii/service.py +56 -0
  34. pycarlo/features/user/__init__.py +4 -0
  35. pycarlo/features/user/exceptions.py +10 -0
  36. pycarlo/features/user/models.py +9 -0
  37. pycarlo/features/user/queries.py +13 -0
  38. pycarlo/features/user/service.py +71 -0
  39. pycarlo/lib/README.md +35 -0
  40. pycarlo/lib/__init__.py +0 -0
  41. pycarlo/lib/schema.json +210020 -0
  42. pycarlo/lib/schema.py +82620 -0
  43. pycarlo/lib/types.py +68 -0
  44. pycarlo-0.12.24.dist-info/LICENSE +201 -0
  45. pycarlo-0.12.24.dist-info/METADATA +249 -0
  46. pycarlo-0.12.24.dist-info/RECORD +48 -0
  47. pycarlo-0.12.24.dist-info/WHEEL +5 -0
  48. pycarlo-0.12.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,127 @@
1
+ import configparser
2
+ import os
3
+ import uuid
4
+ from dataclasses import InitVar, dataclass, field
5
+ from importlib.metadata import version as get_version
6
+ from typing import Optional
7
+
8
+ from pycarlo.common import get_logger
9
+ from pycarlo.common.errors import InvalidConfigFileError, InvalidSessionError
10
+ from pycarlo.common.settings import (
11
+ DEFAULT_CONFIG_PATH,
12
+ DEFAULT_MCD_API_ENDPOINT,
13
+ DEFAULT_MCD_API_ENDPOINT_CONFIG_KEY,
14
+ DEFAULT_MCD_API_ID_CONFIG_KEY,
15
+ DEFAULT_MCD_API_TOKEN_CONFIG_KEY,
16
+ DEFAULT_MCD_IGW_ENDPOINT,
17
+ DEFAULT_PACKAGE_NAME,
18
+ DEFAULT_PROFILE_NAME,
19
+ MCD_API_ENDPOINT,
20
+ MCD_DEFAULT_API_ID,
21
+ MCD_DEFAULT_API_TOKEN,
22
+ MCD_DEFAULT_PROFILE,
23
+ MCD_USER_ID_HEADER,
24
+ PROFILE_FILE_NAME,
25
+ )
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ @dataclass
31
+ class Session:
32
+ """
33
+ Creates an MC access session.
34
+
35
+ Auth resolution hierarchy -
36
+ 1. Passing credentials (mcd_id & mcd_token)
37
+ 2. Environment variables (MCD_DEFAULT_API_ID & MCD_DEFAULT_API_TOKEN)
38
+ 3. Config-file by passing passing profile name (mcd_profile)
39
+ 4. Config-file by setting the profile as an environment variable (MCD_DEFAULT_PROFILE)
40
+ 5. Config-file by default profile name (default)
41
+
42
+ Environment vars can be mixed with passed credentials, but not the config-file profile.
43
+
44
+ If necessary the MC API url can be overridden by specifying an endpoint.
45
+
46
+ The config-file path can be set via mcd_config_path.
47
+
48
+ An optional scope can be set to configure the Session to use the Integration Gateway
49
+ REST API instead of the GraphQL API.
50
+ """
51
+
52
+ mcd_id: InitVar[Optional[str]] = None
53
+ mcd_token: InitVar[Optional[str]] = None
54
+ mcd_profile: InitVar[Optional[str]] = None
55
+ mcd_config_path: InitVar[str] = DEFAULT_CONFIG_PATH
56
+
57
+ id: str = field(init=False)
58
+ token: str = field(init=False)
59
+ session_name: str = field(init=False)
60
+ endpoint: str = DEFAULT_MCD_API_ENDPOINT
61
+ user_id: Optional[str] = MCD_USER_ID_HEADER
62
+ scope: Optional[str] = None
63
+
64
+ def __post_init__(
65
+ self,
66
+ mcd_id: Optional[str],
67
+ mcd_token: Optional[str],
68
+ mcd_profile: Optional[str],
69
+ mcd_config_path: str,
70
+ ):
71
+ version = get_version(DEFAULT_PACKAGE_NAME)
72
+ self.session_name = f"python-sdk-{version}-{uuid.uuid4()}"
73
+ logger.info(f"Creating named session as '{self.session_name}'.")
74
+
75
+ mcd_id = mcd_id or MCD_DEFAULT_API_ID
76
+ mcd_token = mcd_token or MCD_DEFAULT_API_TOKEN
77
+ if mcd_id and mcd_token:
78
+ self.id = mcd_id
79
+ self.token = mcd_token
80
+ elif mcd_id or mcd_token:
81
+ raise InvalidSessionError("Partially setting a session is not supported.")
82
+ else:
83
+ self._read_config(
84
+ mcd_profile=mcd_profile or MCD_DEFAULT_PROFILE or DEFAULT_PROFILE_NAME,
85
+ mcd_config_path=mcd_config_path,
86
+ )
87
+
88
+ if MCD_API_ENDPOINT:
89
+ self.endpoint = MCD_API_ENDPOINT
90
+ elif self.scope and self.endpoint == DEFAULT_MCD_API_ENDPOINT:
91
+ # if scope is set and endpoint is the default one, change it to IGW
92
+ self.endpoint = DEFAULT_MCD_IGW_ENDPOINT
93
+
94
+ session_type = "GATEWAY_API" if self.scope else "APPLICATION_API"
95
+ logger.info(f"Created {session_type} session with MC API ID '{self.id}'.")
96
+
97
+ def _read_config(self, mcd_profile: str, mcd_config_path: str) -> None:
98
+ """
99
+ Return configuration from section (profile name) if it exists.
100
+ """
101
+ config_parser = Session._get_config_parser()
102
+ file_path = os.path.join(mcd_config_path, PROFILE_FILE_NAME)
103
+ logger.info(
104
+ "No provided connection details. Looking up session values from "
105
+ f"'{mcd_profile}' in '{file_path}'."
106
+ )
107
+
108
+ try:
109
+ config_parser.read(file_path)
110
+ self.id = config_parser.get(mcd_profile, DEFAULT_MCD_API_ID_CONFIG_KEY)
111
+ self.token = config_parser.get(mcd_profile, DEFAULT_MCD_API_TOKEN_CONFIG_KEY)
112
+ self.endpoint = config_parser.get(
113
+ mcd_profile,
114
+ DEFAULT_MCD_API_ENDPOINT_CONFIG_KEY,
115
+ fallback=DEFAULT_MCD_API_ENDPOINT,
116
+ )
117
+ except configparser.NoSectionError:
118
+ raise InvalidSessionError(f"Profile '{mcd_profile}' not found in '{file_path}'.")
119
+ except Exception as err:
120
+ raise InvalidConfigFileError from err
121
+
122
+ @staticmethod
123
+ def _get_config_parser() -> configparser.ConfigParser:
124
+ """
125
+ Gets a configparser
126
+ """
127
+ return configparser.ConfigParser()
@@ -0,0 +1,10 @@
1
+ from random import randrange
2
+
3
+
4
+ def downtime() -> None:
5
+ """
6
+ A bit of fun. Demos SDK feature extensions.
7
+ """
8
+ if randrange(10) >= 5:
9
+ raise SystemExit("Bad luck.")
10
+ print("No data downtime found.")
@@ -0,0 +1,3 @@
1
+ from pycarlo.features.circuit_breakers.service import CircuitBreakerService
2
+
3
+ __all__ = ["CircuitBreakerService"]
@@ -0,0 +1,10 @@
1
+ from typing import Any
2
+
3
+
4
+ class CircuitBreakerPipelineException(Exception):
5
+ pass
6
+
7
+
8
+ class CircuitBreakerPollException(Exception):
9
+ def __init__(self, msg: str = "Polling timed out or contains a malformed log.", *args: Any):
10
+ super().__init__(msg, *args)
@@ -0,0 +1,346 @@
1
+ import json
2
+ import time
3
+ from typing import Callable, Dict, List, Optional, Sequence, Union, cast
4
+ from uuid import UUID
5
+
6
+ from box import Box
7
+
8
+ from pycarlo.common import get_logger
9
+ from pycarlo.common.settings import (
10
+ HEADER_MCD_TELEMETRY_REASON,
11
+ HEADER_MCD_TELEMETRY_SERVICE,
12
+ RequestReason,
13
+ )
14
+ from pycarlo.common.utils import boxify
15
+ from pycarlo.core import Client, Mutation, Query
16
+ from pycarlo.features.circuit_breakers.exceptions import (
17
+ CircuitBreakerPipelineException,
18
+ CircuitBreakerPollException,
19
+ )
20
+ from pycarlo.lib.schema import CircuitBreakerState, SqlJobCheckpointStatus
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class CircuitBreakerService:
26
+ _TERM_STATES = {"PROCESSING_COMPLETE", "HAS_ERROR"}
27
+
28
+ def __init__(
29
+ self,
30
+ mc_client: Optional[Client] = None,
31
+ print_func: Callable = logger.info,
32
+ ):
33
+ """
34
+ Convenience methods to help with using circuit breaker rules.
35
+
36
+ :param mc_client: MCD client (e.g. for creating a custom session); created otherwise.
37
+ :param print_func: Function to use for echoing. Uses python logging by default, which
38
+ requires setting MCD_VERBOSE_ERRORS.
39
+ """
40
+ self._client = mc_client or Client()
41
+ self._print_func = print_func
42
+
43
+ def trigger_and_poll(
44
+ self,
45
+ rule_uuid: Optional[Union[str, UUID]] = None,
46
+ namespace: Optional[str] = None,
47
+ rule_name: Optional[str] = None,
48
+ timeout_in_minutes: int = 5,
49
+ runtime_variables: Optional[Dict[str, str]] = None,
50
+ ) -> Optional[bool]:
51
+ """
52
+ Convenience method to both trigger and poll (wait) on circuit breaker rule execution.
53
+
54
+ :param rule_uuid: UUID of the rule (custom SQL monitor) to execute.
55
+ :param namespace: namespace of the rule (custom SQL monitor) to execute.
56
+ :param rule_name: name of the rule (custom SQL monitor) to execute.
57
+ :param timeout_in_minutes: Polling timeout in minutes. See poll() for details.
58
+ :param runtime_variables: runtime variables to use when executing the rule
59
+ :return: True if rule execution has breach; False otherwise. See poll() for any
60
+ exceptions raised.
61
+ """
62
+ breaches = self.poll_all(
63
+ job_execution_uuids=self.trigger_all(
64
+ rule_uuid=rule_uuid,
65
+ namespace=namespace,
66
+ rule_name=rule_name,
67
+ runtime_variables=runtime_variables,
68
+ ),
69
+ timeout_in_minutes=timeout_in_minutes,
70
+ )
71
+ return bool(breaches > 0)
72
+
73
+ def trigger(
74
+ self,
75
+ rule_uuid: Optional[Union[str, UUID]] = None,
76
+ namespace: Optional[str] = None,
77
+ rule_name: Optional[str] = None,
78
+ ) -> str:
79
+ """
80
+ Trigger a rule to start execution with circuit breaker checkpointing.
81
+
82
+ :param rule_uuid: UUID of the rule (custom SQL monitor) to execute.
83
+ :param namespace: namespace of the rule (custom SQL monitor) to execute.
84
+ :param rule_name: name of the rule (custom SQL monitor) to execute.
85
+ :return: Job execution UUID, as a string, to be used to retrieve execution state / status.
86
+ """
87
+ mutation = Mutation()
88
+
89
+ if rule_uuid:
90
+ mutation.trigger_circuit_breaker_rule(rule_uuid=str(rule_uuid)).__fields__(
91
+ "job_execution_uuid"
92
+ )
93
+ elif rule_name:
94
+ if namespace:
95
+ mutation.trigger_circuit_breaker_rule(
96
+ namespace=namespace, rule_name=rule_name
97
+ ).__fields__("job_execution_uuid")
98
+ else:
99
+ mutation.trigger_circuit_breaker_rule(rule_name=rule_name).__fields__(
100
+ "job_execution_uuid"
101
+ )
102
+ else:
103
+ raise ValueError("rule UUID or namespace and rule name must be specified")
104
+
105
+ mutation_client = self._client(
106
+ mutation,
107
+ additional_headers={
108
+ HEADER_MCD_TELEMETRY_REASON: RequestReason.SERVICE.value,
109
+ HEADER_MCD_TELEMETRY_SERVICE: "circuit_breaker_service",
110
+ },
111
+ )
112
+ job_execution_uuid = mutation_client.trigger_circuit_breaker_rule.job_execution_uuid
113
+ self._print_func(
114
+ f"Triggered rule with ID '{rule_uuid}'. "
115
+ f"Received '{job_execution_uuid}' as execution ID."
116
+ )
117
+ return cast(str, job_execution_uuid)
118
+
119
+ def trigger_all(
120
+ self,
121
+ rule_uuid: Optional[Union[str, UUID]] = None,
122
+ namespace: Optional[str] = None,
123
+ rule_name: Optional[str] = None,
124
+ runtime_variables: Optional[Dict[str, str]] = None,
125
+ ) -> List[str]:
126
+ """
127
+ Trigger a rule to start execution with circuit breaker checkpointing.
128
+
129
+ This function supports rules that create multiple executions (e.g. rules with variables
130
+ or over multiple tables)
131
+
132
+ :param rule_uuid: UUID of the rule (custom SQL monitor) to execute.
133
+ :param namespace: namespace of the rule (custom SQL monitor) to execute.
134
+ :param rule_name: name of the rule (custom SQL monitor) to execute.
135
+ :param runtime_variables: runtime variables to use when executing the rule
136
+ :return: Job execution UUIDs, as strings, to be used to retrieve execution state / status.
137
+ """
138
+ mutation = Mutation()
139
+
140
+ runtime_variables_list: Optional[List[Dict[str, str]]]
141
+ if runtime_variables:
142
+ runtime_variables_list = [
143
+ {"name": key, "value": value} for key, value in runtime_variables.items()
144
+ ]
145
+ else:
146
+ runtime_variables_list = None
147
+
148
+ if rule_uuid:
149
+ mutation.trigger_circuit_breaker_rule_v2(
150
+ rule_uuid=str(rule_uuid),
151
+ **({"runtime_variables": runtime_variables_list} if runtime_variables else {}),
152
+ ).__fields__("job_execution_uuids")
153
+ elif rule_name:
154
+ if namespace:
155
+ mutation.trigger_circuit_breaker_rule_v2(
156
+ namespace=namespace,
157
+ rule_name=rule_name,
158
+ **({"runtime_variables": runtime_variables_list} if runtime_variables else {}),
159
+ ).__fields__("job_execution_uuids")
160
+ else:
161
+ mutation.trigger_circuit_breaker_rule_v2(
162
+ rule_name=rule_name,
163
+ **({"runtime_variables": runtime_variables_list} if runtime_variables else {}),
164
+ ).__fields__("job_execution_uuids")
165
+ else:
166
+ raise ValueError("rule UUID or namespace and rule name must be specified")
167
+
168
+ job_execution_uuids = [
169
+ str(id)
170
+ for id in self._client(
171
+ mutation,
172
+ additional_headers={
173
+ HEADER_MCD_TELEMETRY_REASON: RequestReason.SERVICE.value,
174
+ HEADER_MCD_TELEMETRY_SERVICE: "circuit_breaker_service",
175
+ },
176
+ ).trigger_circuit_breaker_rule_v2.job_execution_uuids
177
+ ]
178
+ self._print_func(
179
+ f"Triggered rule with ID '{rule_uuid}'. "
180
+ f"Received {job_execution_uuids} as execution IDs."
181
+ )
182
+ return job_execution_uuids
183
+
184
+ def poll(
185
+ self,
186
+ job_execution_uuid: Union[str, UUID],
187
+ timeout_in_minutes: int = 5,
188
+ ) -> Optional[int]:
189
+ """
190
+ Poll status / state of an execution for a triggered rule. Polls until status is in a term
191
+ state or timeout.
192
+
193
+ :param job_execution_uuid: UUID for the job execution of a rule (custom SQL monitor).
194
+ :param timeout_in_minutes: Polling timeout in minutes. Note that The Data Collector Lambda
195
+ has a max timeout of 15 minutes when executing a query. Queries
196
+ that take longer to execute are not supported, so we recommend
197
+ filtering down the query output to improve performance (e.g limit
198
+ WHERE clause). If you expect a query to take the full 15 minutes
199
+ we recommend padding the timeout to 20 minutes.
200
+ :return: Breach count across all executions. A greater than 0 value indicates a breach.
201
+ :raise CircuitBreakerPipelineException: An error in executing the
202
+ rule (e.g. error in query).
203
+ :raise CircuitBreakerPollException: A timeout during polling or a malformed response.
204
+ """
205
+ return self.poll_all([job_execution_uuid], timeout_in_minutes=timeout_in_minutes)
206
+
207
+ def poll_all(
208
+ self,
209
+ job_execution_uuids: Sequence[Union[str, UUID]],
210
+ timeout_in_minutes: int = 5,
211
+ ) -> int:
212
+ """
213
+ Poll status / state of executions for a triggered rule. Polls until status is in a term
214
+ state or timeout.
215
+
216
+ :param job_execution_uuids: UUIDs for the job executions of a rule (custom SQL monitor).
217
+ :param timeout_in_minutes: Polling timeout in minutes. Note that The Data Collector Lambda
218
+ has a max timeout of 15 minutes when executing a query. Queries
219
+ that take longer to execute are not supported, so we recommend
220
+ filtering down the query output to improve performance (e.g limit
221
+ WHERE clause). If you expect a query to take the full 15 minutes
222
+ we recommend padding the timeout to 20 minutes.
223
+ :return: Breach count. A greater than 0 value indicates a breach.
224
+ :raise CircuitBreakerPipelineException: An error in executing the
225
+ rule (e.g. error in query).
226
+ :raise CircuitBreakerPollException: A timeout during polling or a malformed response.
227
+ """
228
+ logs = cast(
229
+ List[Box],
230
+ self._poll(
231
+ job_execution_uuids=job_execution_uuids,
232
+ timeout_in_minutes=timeout_in_minutes,
233
+ ),
234
+ )
235
+
236
+ if not logs:
237
+ raise CircuitBreakerPollException
238
+
239
+ self._print_func(
240
+ "Completed polling. Retrieved execution with logs "
241
+ f"{list(map(str, logs))} for IDs {job_execution_uuids}."
242
+ )
243
+
244
+ breaches = 0
245
+ has_breaches = False
246
+ if logs and len(logs) > 0:
247
+ for log in logs:
248
+ if log.payload.error:
249
+ logs_str = "\n".join(str(log) for log in logs)
250
+ raise CircuitBreakerPipelineException(
251
+ f"Execution pipeline errored out. Details:\n{logs_str}"
252
+ )
253
+ if log.payload.breach_count is not None:
254
+ breaches += log.payload.breach_count
255
+ has_breaches = True
256
+
257
+ if not has_breaches:
258
+ raise CircuitBreakerPollException
259
+
260
+ return breaches
261
+
262
+ @boxify(use_snakes=True, default_box_attr=None, default_box=True)
263
+ def _poll(
264
+ self,
265
+ job_execution_uuids: Sequence[Union[str, UUID]],
266
+ timeout_in_minutes: int,
267
+ sleep_interval_in_seconds: int = 15,
268
+ ) -> Optional[List[Box]]:
269
+ timeout_start = time.time()
270
+ while time.time() < timeout_start + 60 * timeout_in_minutes:
271
+ query = Query()
272
+ query.get_circuit_breaker_rule_state_v2(
273
+ job_execution_uuids=map(str, job_execution_uuids)
274
+ ).__fields__("status", "log")
275
+ circuit_rule_breaker_states = cast(
276
+ List[CircuitBreakerState],
277
+ self._client(
278
+ query,
279
+ additional_headers={
280
+ HEADER_MCD_TELEMETRY_REASON: RequestReason.SERVICE.value,
281
+ HEADER_MCD_TELEMETRY_SERVICE: "circuit_breaker_service",
282
+ },
283
+ ).get_circuit_breaker_rule_state_v2,
284
+ )
285
+
286
+ aggregated_status = self._get_aggregated_status(circuit_rule_breaker_states)
287
+ self._print_func(
288
+ f"Retrieved execution with aggregated status '{aggregated_status}' for "
289
+ f"IDs {job_execution_uuids}."
290
+ )
291
+
292
+ if aggregated_status in self._TERM_STATES:
293
+ return self._get_payloads(circuit_rule_breaker_states, aggregated_status)
294
+
295
+ self._print_func(
296
+ f"Aggregated state is not terminal state for IDs {job_execution_uuids}. "
297
+ f"Polling again in '{sleep_interval_in_seconds}' seconds."
298
+ )
299
+ time.sleep(sleep_interval_in_seconds)
300
+
301
+ def _get_log_payload(self, log: str):
302
+ log_entries = json.loads(log)
303
+ log_entries.reverse()
304
+ for entry in log_entries:
305
+ if "payload" in entry:
306
+ return Box(entry, default_box_attr=None, default_box=True)
307
+ return Box()
308
+
309
+ def _get_payloads(
310
+ self,
311
+ states: List[CircuitBreakerState],
312
+ status: SqlJobCheckpointStatus,
313
+ ) -> List[Box]:
314
+ payloads = []
315
+ for state in states:
316
+ if state.status == status:
317
+ payloads.append(self._get_log_payload(str(state.log)))
318
+ return payloads
319
+
320
+ @staticmethod
321
+ def _get_aggregated_status(states: List[CircuitBreakerState]) -> SqlJobCheckpointStatus:
322
+ if not states:
323
+ return SqlJobCheckpointStatus.REGISTERED # type: ignore
324
+
325
+ status_by_state = {}
326
+ for state in states:
327
+ status_by_state.setdefault(state.status, []).append(state)
328
+
329
+ def all_in_state(s: SqlJobCheckpointStatus):
330
+ return len(status_by_state.get(s, [])) == len(states)
331
+
332
+ return (
333
+ SqlJobCheckpointStatus.HAS_ERROR # type: ignore
334
+ if status_by_state.get(SqlJobCheckpointStatus.HAS_ERROR) # type: ignore
335
+ else SqlJobCheckpointStatus.PROCESSING_COMPLETE # type: ignore
336
+ if all_in_state(SqlJobCheckpointStatus.PROCESSING_COMPLETE) # type: ignore
337
+ else SqlJobCheckpointStatus.PROCESSING_START # type: ignore
338
+ if status_by_state.get(SqlJobCheckpointStatus.PROCESSING_COMPLETE) # type: ignore
339
+ or all_in_state(SqlJobCheckpointStatus.PROCESSING_START) # type: ignore
340
+ else SqlJobCheckpointStatus.EXECUTING_COMPLETE # type: ignore
341
+ if all_in_state(SqlJobCheckpointStatus.PROCESSING_COMPLETE) # type: ignore
342
+ else SqlJobCheckpointStatus.EXECUTING_START # type: ignore
343
+ if status_by_state.get(SqlJobCheckpointStatus.EXECUTING_COMPLETE) # type: ignore
344
+ or all_in_state(SqlJobCheckpointStatus.EXECUTING_START) # type: ignore
345
+ else SqlJobCheckpointStatus.REGISTERED # type: ignore
346
+ )
@@ -0,0 +1,3 @@
1
+ from pycarlo.features.dbt.dbt_importer import DbtImporter
2
+
3
+ __all__ = ["DbtImporter"]