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.
Files changed (57) hide show
  1. cloudquery/sdk/__init__.py +0 -0
  2. cloudquery/sdk/internal/__init__.py +0 -0
  3. cloudquery/sdk/internal/checkpoint.py +199 -0
  4. cloudquery/sdk/internal/memdb/__init__.py +1 -0
  5. cloudquery/sdk/internal/memdb/memdb.py +221 -0
  6. cloudquery/sdk/internal/servers/__init__.py +0 -0
  7. cloudquery/sdk/internal/servers/discovery_v1/__init__.py +0 -0
  8. cloudquery/sdk/internal/servers/discovery_v1/discovery.py +11 -0
  9. cloudquery/sdk/internal/servers/plugin_v3/__init__.py +1 -0
  10. cloudquery/sdk/internal/servers/plugin_v3/plugin.py +156 -0
  11. cloudquery/sdk/message/__init__.py +14 -0
  12. cloudquery/sdk/message/read.py +6 -0
  13. cloudquery/sdk/message/sync.py +31 -0
  14. cloudquery/sdk/message/write.py +24 -0
  15. cloudquery/sdk/plugin/__init__.py +1 -0
  16. cloudquery/sdk/plugin/plugin.py +102 -0
  17. cloudquery/sdk/py.typed +0 -0
  18. cloudquery/sdk/scalar/__init__.py +15 -0
  19. cloudquery/sdk/scalar/binary.py +35 -0
  20. cloudquery/sdk/scalar/bool.py +48 -0
  21. cloudquery/sdk/scalar/date32.py +39 -0
  22. cloudquery/sdk/scalar/date64.py +39 -0
  23. cloudquery/sdk/scalar/float.py +49 -0
  24. cloudquery/sdk/scalar/int.py +56 -0
  25. cloudquery/sdk/scalar/json.py +27 -0
  26. cloudquery/sdk/scalar/list.py +56 -0
  27. cloudquery/sdk/scalar/scalar.py +30 -0
  28. cloudquery/sdk/scalar/scalar_factory.py +64 -0
  29. cloudquery/sdk/scalar/string.py +31 -0
  30. cloudquery/sdk/scalar/timestamp.py +41 -0
  31. cloudquery/sdk/scalar/uint.py +58 -0
  32. cloudquery/sdk/scalar/uuid.py +43 -0
  33. cloudquery/sdk/scalar/vector.py +43 -0
  34. cloudquery/sdk/scheduler/__init__.py +3 -0
  35. cloudquery/sdk/scheduler/scheduler.py +267 -0
  36. cloudquery/sdk/scheduler/table_resolver.py +48 -0
  37. cloudquery/sdk/schema/__init__.py +16 -0
  38. cloudquery/sdk/schema/arrow.py +11 -0
  39. cloudquery/sdk/schema/column.py +77 -0
  40. cloudquery/sdk/schema/resource.py +29 -0
  41. cloudquery/sdk/schema/table.py +332 -0
  42. cloudquery/sdk/serve/__init__.py +1 -0
  43. cloudquery/sdk/serve/plugin.py +407 -0
  44. cloudquery/sdk/stateclient/__init__.py +1 -0
  45. cloudquery/sdk/stateclient/keys.py +44 -0
  46. cloudquery/sdk/stateclient/stateclient.py +278 -0
  47. cloudquery/sdk/transformers/__init__.py +2 -0
  48. cloudquery/sdk/transformers/openapi.py +49 -0
  49. cloudquery/sdk/transformers/transformers.py +13 -0
  50. cloudquery/sdk/types/__init__.py +2 -0
  51. cloudquery/sdk/types/json.py +26 -0
  52. cloudquery/sdk/types/uuid.py +28 -0
  53. cloudquery_plugin_sdk-0.1.55.dist-info/METADATA +50 -0
  54. cloudquery_plugin_sdk-0.1.55.dist-info/RECORD +57 -0
  55. cloudquery_plugin_sdk-0.1.55.dist-info/WHEEL +5 -0
  56. cloudquery_plugin_sdk-0.1.55.dist-info/licenses/LICENSE +353 -0
  57. 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