cloudquery-plugin-sdk 0.1.55__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.
- cloudquery/sdk/__init__.py +0 -0
- cloudquery/sdk/internal/__init__.py +0 -0
- cloudquery/sdk/internal/checkpoint.py +199 -0
- cloudquery/sdk/internal/memdb/__init__.py +1 -0
- cloudquery/sdk/internal/memdb/memdb.py +221 -0
- cloudquery/sdk/internal/servers/__init__.py +0 -0
- cloudquery/sdk/internal/servers/discovery_v1/__init__.py +0 -0
- cloudquery/sdk/internal/servers/discovery_v1/discovery.py +11 -0
- cloudquery/sdk/internal/servers/plugin_v3/__init__.py +1 -0
- cloudquery/sdk/internal/servers/plugin_v3/plugin.py +156 -0
- cloudquery/sdk/message/__init__.py +14 -0
- cloudquery/sdk/message/read.py +6 -0
- cloudquery/sdk/message/sync.py +31 -0
- cloudquery/sdk/message/write.py +24 -0
- cloudquery/sdk/plugin/__init__.py +1 -0
- cloudquery/sdk/plugin/plugin.py +102 -0
- cloudquery/sdk/py.typed +0 -0
- cloudquery/sdk/scalar/__init__.py +15 -0
- cloudquery/sdk/scalar/binary.py +35 -0
- cloudquery/sdk/scalar/bool.py +48 -0
- cloudquery/sdk/scalar/date32.py +39 -0
- cloudquery/sdk/scalar/date64.py +39 -0
- cloudquery/sdk/scalar/float.py +49 -0
- cloudquery/sdk/scalar/int.py +56 -0
- cloudquery/sdk/scalar/json.py +27 -0
- cloudquery/sdk/scalar/list.py +56 -0
- cloudquery/sdk/scalar/scalar.py +30 -0
- cloudquery/sdk/scalar/scalar_factory.py +64 -0
- cloudquery/sdk/scalar/string.py +31 -0
- cloudquery/sdk/scalar/timestamp.py +41 -0
- cloudquery/sdk/scalar/uint.py +58 -0
- cloudquery/sdk/scalar/uuid.py +43 -0
- cloudquery/sdk/scalar/vector.py +43 -0
- cloudquery/sdk/scheduler/__init__.py +3 -0
- cloudquery/sdk/scheduler/scheduler.py +267 -0
- cloudquery/sdk/scheduler/table_resolver.py +48 -0
- cloudquery/sdk/schema/__init__.py +16 -0
- cloudquery/sdk/schema/arrow.py +11 -0
- cloudquery/sdk/schema/column.py +77 -0
- cloudquery/sdk/schema/resource.py +29 -0
- cloudquery/sdk/schema/table.py +332 -0
- cloudquery/sdk/serve/__init__.py +1 -0
- cloudquery/sdk/serve/plugin.py +407 -0
- cloudquery/sdk/stateclient/__init__.py +1 -0
- cloudquery/sdk/stateclient/keys.py +44 -0
- cloudquery/sdk/stateclient/stateclient.py +278 -0
- cloudquery/sdk/transformers/__init__.py +2 -0
- cloudquery/sdk/transformers/openapi.py +49 -0
- cloudquery/sdk/transformers/transformers.py +13 -0
- cloudquery/sdk/types/__init__.py +2 -0
- cloudquery/sdk/types/json.py +26 -0
- cloudquery/sdk/types/uuid.py +28 -0
- cloudquery_plugin_sdk-0.1.55.dist-info/METADATA +50 -0
- cloudquery_plugin_sdk-0.1.55.dist-info/RECORD +57 -0
- cloudquery_plugin_sdk-0.1.55.dist-info/WHEEL +5 -0
- cloudquery_plugin_sdk-0.1.55.dist-info/licenses/LICENSE +353 -0
- cloudquery_plugin_sdk-0.1.55.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from enum import IntEnum
|
|
5
|
+
import fnmatch
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
import pyarrow as pa
|
|
9
|
+
|
|
10
|
+
from cloudquery.sdk.schema import arrow
|
|
11
|
+
from .column import Column
|
|
12
|
+
|
|
13
|
+
CQ_SYNC_TIME_COLUMN = "cq_sync_time"
|
|
14
|
+
CQ_SOURCE_NAME_COLUMN = "cq_source_name"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Client:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Table:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
name: str,
|
|
25
|
+
columns: List[Column],
|
|
26
|
+
title: str = "",
|
|
27
|
+
description: str = "",
|
|
28
|
+
parent: Table = None,
|
|
29
|
+
relations: List[Table] = None,
|
|
30
|
+
is_incremental: bool = False,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.name = name
|
|
33
|
+
self.columns = columns
|
|
34
|
+
self.title = title
|
|
35
|
+
self.description = description
|
|
36
|
+
if relations is None:
|
|
37
|
+
relations = []
|
|
38
|
+
self.parent = parent
|
|
39
|
+
self.relations = relations
|
|
40
|
+
self.is_incremental = is_incremental
|
|
41
|
+
|
|
42
|
+
def multiplex(self, client: Client) -> List[Client]:
|
|
43
|
+
return [client]
|
|
44
|
+
|
|
45
|
+
def index_column(self, column_name: str) -> int:
|
|
46
|
+
for i, column in enumerate(self.columns):
|
|
47
|
+
if column.name == column_name:
|
|
48
|
+
return i
|
|
49
|
+
raise ValueError(f"Column {column_name} not found")
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def resolver(self):
|
|
53
|
+
raise NotImplementedError
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def primary_keys(self):
|
|
57
|
+
return [column.name for column in self.columns if column.primary_key]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def incremental_keys(self):
|
|
61
|
+
return [column.name for column in self.columns if column.incremental_key]
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_arrow_schema(cls, schema: pa.Schema) -> Table:
|
|
65
|
+
columns = []
|
|
66
|
+
for field in schema:
|
|
67
|
+
columns.append(Column.from_arrow_field(field))
|
|
68
|
+
parent = None
|
|
69
|
+
if arrow.METADATA_TABLE_DEPENDS_ON in schema.metadata:
|
|
70
|
+
parent = Table(
|
|
71
|
+
name=schema.metadata[arrow.METADATA_TABLE_DEPENDS_ON].decode("utf-8"),
|
|
72
|
+
columns=[],
|
|
73
|
+
)
|
|
74
|
+
return cls(
|
|
75
|
+
name=schema.metadata.get(arrow.METADATA_TABLE_NAME, b"").decode("utf-8"),
|
|
76
|
+
title=schema.metadata.get(arrow.METADATA_TABLE_TITLE, b"").decode("utf-8"),
|
|
77
|
+
columns=columns,
|
|
78
|
+
description=schema.metadata.get(arrow.METADATA_TABLE_DESCRIPTION).decode(
|
|
79
|
+
"utf-8"
|
|
80
|
+
),
|
|
81
|
+
is_incremental=schema.metadata.get(
|
|
82
|
+
arrow.METADATA_INCREMENTAL, arrow.METADATA_FALSE
|
|
83
|
+
)
|
|
84
|
+
== arrow.METADATA_TRUE,
|
|
85
|
+
parent=parent,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def to_arrow_schema(self):
|
|
89
|
+
fields = []
|
|
90
|
+
md = {
|
|
91
|
+
arrow.METADATA_TABLE_NAME: self.name,
|
|
92
|
+
arrow.METADATA_TABLE_DESCRIPTION: self.description,
|
|
93
|
+
arrow.METADATA_TABLE_TITLE: self.title,
|
|
94
|
+
arrow.METADATA_TABLE_DEPENDS_ON: self.parent.name if self.parent else "",
|
|
95
|
+
arrow.METADATA_INCREMENTAL: (
|
|
96
|
+
arrow.METADATA_TRUE if self.is_incremental else arrow.METADATA_FALSE
|
|
97
|
+
),
|
|
98
|
+
}
|
|
99
|
+
for column in self.columns:
|
|
100
|
+
fields.append(column.to_arrow_field())
|
|
101
|
+
|
|
102
|
+
return pa.schema(fields, metadata=md)
|
|
103
|
+
|
|
104
|
+
def __lt__(self, other):
|
|
105
|
+
return self.name < other.name
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def tables_to_arrow_schemas(tables: List[Table]):
|
|
109
|
+
schemas = []
|
|
110
|
+
for table in tables:
|
|
111
|
+
schemas.append(table.to_arrow_schema())
|
|
112
|
+
return schemas
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def filter_dfs(
|
|
116
|
+
tables: List[Table],
|
|
117
|
+
include_tables: List[str],
|
|
118
|
+
skip_tables: List[str],
|
|
119
|
+
skip_dependent_tables: bool = False,
|
|
120
|
+
) -> List[Table]:
|
|
121
|
+
flattened_tables = flatten_tables(tables)
|
|
122
|
+
for include_pattern in include_tables:
|
|
123
|
+
matched = any(
|
|
124
|
+
fnmatch.fnmatch(table.name, include_pattern) for table in flattened_tables
|
|
125
|
+
)
|
|
126
|
+
if not matched:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"tables include a pattern {include_pattern} with no matches"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
for exclude_pattern in skip_tables:
|
|
132
|
+
matched = any(
|
|
133
|
+
fnmatch.fnmatch(table.name, exclude_pattern) for table in flattened_tables
|
|
134
|
+
)
|
|
135
|
+
if not matched:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"skip_tables include a pattern {exclude_pattern} with no matches"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def include_func(t):
|
|
141
|
+
return any(
|
|
142
|
+
fnmatch.fnmatch(t.name, include_pattern)
|
|
143
|
+
for include_pattern in include_tables
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def exclude_func(t):
|
|
147
|
+
return any(
|
|
148
|
+
fnmatch.fnmatch(t.name, exclude_pattern) for exclude_pattern in skip_tables
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return filter_dfs_func(tables, include_func, exclude_func, skip_dependent_tables)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def filter_dfs_func(tt: List[Table], include, exclude, skip_dependent_tables: bool):
|
|
155
|
+
filtered_tables = []
|
|
156
|
+
for t in tt:
|
|
157
|
+
filtered_table = copy.deepcopy(t)
|
|
158
|
+
for r in filtered_table.relations:
|
|
159
|
+
r.parent = filtered_table
|
|
160
|
+
filtered_table = _filter_dfs_impl(
|
|
161
|
+
filtered_table, False, include, exclude, skip_dependent_tables
|
|
162
|
+
)
|
|
163
|
+
if filtered_table is not None:
|
|
164
|
+
filtered_tables.append(filtered_table)
|
|
165
|
+
return filtered_tables
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _filter_dfs_impl(t, parent_matched, include, exclude, skip_dependent_tables):
|
|
169
|
+
def filter_dfs_child(r, matched, include, exclude, skip_dependent_tables):
|
|
170
|
+
filtered_child = _filter_dfs_impl(
|
|
171
|
+
r, matched, include, exclude, skip_dependent_tables
|
|
172
|
+
)
|
|
173
|
+
if filtered_child is not None:
|
|
174
|
+
return True, r
|
|
175
|
+
return matched, None
|
|
176
|
+
|
|
177
|
+
if exclude(t):
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
matched = parent_matched and not skip_dependent_tables
|
|
181
|
+
if include(t):
|
|
182
|
+
matched = True
|
|
183
|
+
|
|
184
|
+
filtered_relations = []
|
|
185
|
+
for r in t.relations:
|
|
186
|
+
matched, filtered_child = filter_dfs_child(
|
|
187
|
+
r, matched, include, exclude, skip_dependent_tables
|
|
188
|
+
)
|
|
189
|
+
if filtered_child is not None:
|
|
190
|
+
filtered_relations.append(filtered_child)
|
|
191
|
+
|
|
192
|
+
t.relations = filtered_relations
|
|
193
|
+
|
|
194
|
+
if matched:
|
|
195
|
+
return t
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TableColumnChangeType:
|
|
200
|
+
ADD = 1
|
|
201
|
+
REMOVE = 2
|
|
202
|
+
REMOVE_UNIQUE_CONSTRAINT = 3
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class TableColumnChange:
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
type: TableColumnChangeType,
|
|
209
|
+
column_name: str,
|
|
210
|
+
current: Optional[Column],
|
|
211
|
+
previous: Optional[Column],
|
|
212
|
+
):
|
|
213
|
+
self.type = type
|
|
214
|
+
self.column_name = column_name
|
|
215
|
+
self.current = current
|
|
216
|
+
self.previous = previous
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class TableColumnChangeType(IntEnum):
|
|
220
|
+
UNKNOWN = 0
|
|
221
|
+
ADD = 1
|
|
222
|
+
UPDATE = 2
|
|
223
|
+
REMOVE = 3
|
|
224
|
+
REMOVE_UNIQUE_CONSTRAINT = 4
|
|
225
|
+
MOVE_TO_CQ_ONLY = 5
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_table_changes(new: Table, old: Table) -> List[TableColumnChange]:
|
|
229
|
+
changes = []
|
|
230
|
+
|
|
231
|
+
# Special case: Moving from individual PKs to singular PK on _cq_id
|
|
232
|
+
new_pks = new.primary_keys
|
|
233
|
+
if (
|
|
234
|
+
len(new_pks) == 1
|
|
235
|
+
and new_pks[0] == "CqIDColumn"
|
|
236
|
+
and get_table_column(old, "CqIDColumn") is None
|
|
237
|
+
and len(old.primary_keys) > 0
|
|
238
|
+
):
|
|
239
|
+
changes.append(
|
|
240
|
+
TableColumnChange(
|
|
241
|
+
type=TableColumnChangeType.MOVE_TO_CQ_ONLY,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
for c in new.columns:
|
|
246
|
+
other_column = get_table_column(old, c.name)
|
|
247
|
+
# A column was added to the table definition
|
|
248
|
+
if other_column is None:
|
|
249
|
+
changes.append(
|
|
250
|
+
TableColumnChange(
|
|
251
|
+
type=TableColumnChangeType.ADD,
|
|
252
|
+
column_name=c.name,
|
|
253
|
+
current=c,
|
|
254
|
+
previous=None,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
# Column type or options (e.g. PK, Not Null) changed in the new table definition
|
|
260
|
+
if (
|
|
261
|
+
c.type != other_column.type
|
|
262
|
+
or c.not_null != other_column.not_null
|
|
263
|
+
or c.primary_key != other_column.primary_key
|
|
264
|
+
):
|
|
265
|
+
changes.append(
|
|
266
|
+
TableColumnChange(
|
|
267
|
+
type=TableColumnChangeType.UPDATE,
|
|
268
|
+
column_name=c.name,
|
|
269
|
+
current=c,
|
|
270
|
+
previous=other_column,
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Unique constraint was removed
|
|
275
|
+
if not c.unique and other_column.unique:
|
|
276
|
+
changes.append(
|
|
277
|
+
TableColumnChange(
|
|
278
|
+
type=TableColumnChangeType.REMOVE_UNIQUE_CONSTRAINT,
|
|
279
|
+
column_name=c.name,
|
|
280
|
+
current=c,
|
|
281
|
+
previous=other_column,
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# A column was removed from the table definition
|
|
286
|
+
for c in old.columns:
|
|
287
|
+
if get_table_column(new, c.name) is None:
|
|
288
|
+
changes.append(
|
|
289
|
+
TableColumnChange(
|
|
290
|
+
type=TableColumnChangeType.REMOVE,
|
|
291
|
+
column_name=c.name,
|
|
292
|
+
current=None,
|
|
293
|
+
previous=c,
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return changes
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def get_table_column(table: Table, column_name: str) -> Optional[Column]:
|
|
301
|
+
for c in table.columns:
|
|
302
|
+
if c.name == column_name:
|
|
303
|
+
return c
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def flatten_tables_recursive(original_tables: List[Table]) -> List[Table]:
|
|
308
|
+
tables = []
|
|
309
|
+
for table in original_tables:
|
|
310
|
+
table_copy = Table(
|
|
311
|
+
name=table.name,
|
|
312
|
+
columns=table.columns,
|
|
313
|
+
relations=table.relations,
|
|
314
|
+
title=table.title,
|
|
315
|
+
description=table.description,
|
|
316
|
+
is_incremental=table.is_incremental,
|
|
317
|
+
parent=table.parent,
|
|
318
|
+
)
|
|
319
|
+
tables.append(table_copy)
|
|
320
|
+
tables.extend(flatten_tables_recursive(table.relations))
|
|
321
|
+
return tables
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def flatten_tables(original_tables: List[Table]) -> List[Table]:
|
|
325
|
+
tables = flatten_tables_recursive(original_tables)
|
|
326
|
+
seen = set()
|
|
327
|
+
deduped = []
|
|
328
|
+
for table in tables:
|
|
329
|
+
if table.name not in seen:
|
|
330
|
+
deduped.append(table)
|
|
331
|
+
seen.add(table.name)
|
|
332
|
+
return deduped
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .plugin import PluginCommand
|