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,58 @@
|
|
|
1
|
+
from cloudquery.sdk.scalar import Scalar, ScalarInvalidTypeError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Uint(Scalar):
|
|
5
|
+
def __init__(self, valid: bool = False, value: any = None, bitwidth: int = 64):
|
|
6
|
+
self._bitwidth = bitwidth
|
|
7
|
+
self._max = 2**bitwidth
|
|
8
|
+
super().__init__(valid, value)
|
|
9
|
+
|
|
10
|
+
def __eq__(self, scalar: Scalar) -> bool:
|
|
11
|
+
if scalar is None:
|
|
12
|
+
return False
|
|
13
|
+
if isinstance(scalar, Uint):
|
|
14
|
+
return (
|
|
15
|
+
self._bitwidth == scalar.bitwidth
|
|
16
|
+
and self._value == scalar._value
|
|
17
|
+
and self._valid == scalar._valid
|
|
18
|
+
)
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def bitwidth(self):
|
|
23
|
+
return self._bitwidth
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def value(self):
|
|
27
|
+
return self._value
|
|
28
|
+
|
|
29
|
+
def set(self, value: any):
|
|
30
|
+
if value is None:
|
|
31
|
+
self._valid = False
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if isinstance(value, Uint) and value.bitwidth == self._bitwidth:
|
|
35
|
+
self._valid = value.is_valid
|
|
36
|
+
self._value = value.value
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if isinstance(value, int):
|
|
40
|
+
val = value
|
|
41
|
+
elif isinstance(value, float):
|
|
42
|
+
val = int(value)
|
|
43
|
+
elif isinstance(value, str):
|
|
44
|
+
try:
|
|
45
|
+
val = int(value)
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
raise ScalarInvalidTypeError(
|
|
48
|
+
f"Invalid value for Int{self._bitwidth} scalar"
|
|
49
|
+
) from e
|
|
50
|
+
else:
|
|
51
|
+
raise ScalarInvalidTypeError(
|
|
52
|
+
f"Invalid type {type(value)} for Int{self._bitwidth} scalar"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if val < 0 or val >= self._max:
|
|
56
|
+
raise ScalarInvalidTypeError(f"Invalid Uint{self._bitwidth} scalar {val}")
|
|
57
|
+
self._value = val
|
|
58
|
+
self._valid = True
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from cloudquery.sdk.scalar import Scalar, ScalarInvalidTypeError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UUID(Scalar):
|
|
6
|
+
def __init__(self, valid: bool = False, value: uuid.UUID = None):
|
|
7
|
+
if isinstance(value, uuid.UUID):
|
|
8
|
+
value = value.bytes
|
|
9
|
+
super().__init__(valid, value)
|
|
10
|
+
|
|
11
|
+
def __eq__(self, scalar: Scalar) -> bool:
|
|
12
|
+
if scalar is None:
|
|
13
|
+
return False
|
|
14
|
+
if isinstance(scalar, UUID):
|
|
15
|
+
return self._value == scalar._value and self._valid == scalar._valid
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def value(self):
|
|
20
|
+
return self._value
|
|
21
|
+
|
|
22
|
+
def set(self, value: any):
|
|
23
|
+
if value is None:
|
|
24
|
+
self._valid = False
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if isinstance(value, UUID):
|
|
28
|
+
self._valid = value.is_valid
|
|
29
|
+
self._value = value.value
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
if isinstance(value, bytes):
|
|
33
|
+
self._value = value
|
|
34
|
+
elif isinstance(value, uuid.UUID):
|
|
35
|
+
self._value = value.bytes
|
|
36
|
+
elif isinstance(value, str):
|
|
37
|
+
try:
|
|
38
|
+
self._value = uuid.UUID(value).bytes
|
|
39
|
+
except ValueError as e:
|
|
40
|
+
raise ScalarInvalidTypeError("Invalid type for UUID scalar") from e
|
|
41
|
+
else:
|
|
42
|
+
raise ScalarInvalidTypeError(f"Invalid type {type(value)} for UUID scalar")
|
|
43
|
+
self._valid = True
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .scalar import Scalar
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Vector:
|
|
6
|
+
def __init__(self, type: Type[Scalar] = None, *args):
|
|
7
|
+
self.data = []
|
|
8
|
+
self.type = type
|
|
9
|
+
for arg in args:
|
|
10
|
+
self.append(arg) # Use the append method for type checking and appending
|
|
11
|
+
|
|
12
|
+
def append(self, item):
|
|
13
|
+
if not isinstance(item, Scalar):
|
|
14
|
+
raise TypeError("Item is not of type Scalar or its subclass")
|
|
15
|
+
|
|
16
|
+
if self.type is None:
|
|
17
|
+
self.type = type(item)
|
|
18
|
+
self.data.append(item)
|
|
19
|
+
elif isinstance(item, self.type):
|
|
20
|
+
self.data.append(item)
|
|
21
|
+
else:
|
|
22
|
+
raise TypeError(f"Item is not of type {self.type.__name__}")
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other):
|
|
25
|
+
if not isinstance(other, Vector):
|
|
26
|
+
return False
|
|
27
|
+
if len(self) != len(other):
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
for self_item, other_item in zip(self.data, other.data):
|
|
31
|
+
if self_item != other_item:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
def __getitem__(self, index):
|
|
37
|
+
return self.data[index]
|
|
38
|
+
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return len(self.data)
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return f"Vector of {self.type.__name__ if self.type else 'unknown type'}: {self.data}"
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
from concurrent import futures
|
|
3
|
+
from typing import List, Generator, Any, Optional
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from cloudquery.sdk.message import (
|
|
8
|
+
SyncMessage,
|
|
9
|
+
SyncInsertMessage,
|
|
10
|
+
SyncMigrateTableMessage,
|
|
11
|
+
SyncErrorMessage,
|
|
12
|
+
)
|
|
13
|
+
from cloudquery.sdk.internal.checkpoint import Checkpoints, Scope
|
|
14
|
+
from cloudquery.sdk.schema import Resource
|
|
15
|
+
from cloudquery.sdk.stateclient.stateclient import StateClient
|
|
16
|
+
from .table_resolver import TableResolver, Client
|
|
17
|
+
|
|
18
|
+
QUEUE_PER_WORKER = 100
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ThreadPoolExecutorWithQueueSizeLimit(futures.ThreadPoolExecutor):
|
|
22
|
+
def __init__(self, maxsize, *args, **kwargs):
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
self._work_queue = queue.Queue(maxsize=maxsize)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TableResolverStarted:
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TableResolverFinished:
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TableResolversSubmitted:
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Scheduler:
|
|
43
|
+
def __init__(
|
|
44
|
+
self, concurrency: int, queue_size: int = 0, max_depth: int = 3, logger=None
|
|
45
|
+
):
|
|
46
|
+
self._post_sync_hook = lambda: None
|
|
47
|
+
self._queue = queue.Queue()
|
|
48
|
+
# pass to StateClientBuilder.build so that state flushes are sent as checkpoints, when the CLI supports them
|
|
49
|
+
self.checkpoints = Checkpoints()
|
|
50
|
+
self._max_depth = max_depth
|
|
51
|
+
if logger is None:
|
|
52
|
+
self._logger = structlog.get_logger()
|
|
53
|
+
else:
|
|
54
|
+
self._logger = logger
|
|
55
|
+
if concurrency <= 0:
|
|
56
|
+
raise ValueError("concurrency must be greater than 0")
|
|
57
|
+
if max_depth <= 0:
|
|
58
|
+
raise ValueError("max_depth must be greater than 0")
|
|
59
|
+
self._queue_size = (
|
|
60
|
+
queue_size if queue_size > 0 else concurrency * QUEUE_PER_WORKER
|
|
61
|
+
)
|
|
62
|
+
self._pools: List[ThreadPoolExecutorWithQueueSizeLimit] = []
|
|
63
|
+
current_depth_concurrency = concurrency
|
|
64
|
+
current_depth_queue_size = queue_size
|
|
65
|
+
for _ in range(max_depth + 1):
|
|
66
|
+
self._pools.append(
|
|
67
|
+
ThreadPoolExecutorWithQueueSizeLimit(
|
|
68
|
+
maxsize=current_depth_queue_size,
|
|
69
|
+
max_workers=current_depth_concurrency,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
current_depth_concurrency = (
|
|
73
|
+
current_depth_concurrency // 2 if current_depth_concurrency > 1 else 1
|
|
74
|
+
)
|
|
75
|
+
current_depth_queue_size = (
|
|
76
|
+
current_depth_queue_size // 2 if current_depth_queue_size > 1 else 1
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def shutdown(self):
|
|
80
|
+
for pool in self._pools:
|
|
81
|
+
pool.shutdown()
|
|
82
|
+
|
|
83
|
+
def resolve_resource(
|
|
84
|
+
self, resolver: TableResolver, client: Client, parent: Resource, item: Any
|
|
85
|
+
) -> Resource:
|
|
86
|
+
resource = Resource(resolver.table, parent, item)
|
|
87
|
+
resolver.pre_resource_resolve(client, resource)
|
|
88
|
+
for column in resolver.table.columns:
|
|
89
|
+
resolver.resolve_column(client, resource, column.name)
|
|
90
|
+
resolver.post_resource_resolve(client, resource)
|
|
91
|
+
return resource
|
|
92
|
+
|
|
93
|
+
def resolve_table(
|
|
94
|
+
self,
|
|
95
|
+
resolver: TableResolver,
|
|
96
|
+
depth: int,
|
|
97
|
+
client: Client,
|
|
98
|
+
parent_item: Optional[Resource],
|
|
99
|
+
res: queue.Queue,
|
|
100
|
+
scope: Optional[Scope] = None,
|
|
101
|
+
):
|
|
102
|
+
scope = scope or self.checkpoints.new_scope(None)
|
|
103
|
+
try:
|
|
104
|
+
with self.checkpoints.entered(scope):
|
|
105
|
+
self._resolve_table(resolver, depth, client, parent_item, res, scope)
|
|
106
|
+
finally:
|
|
107
|
+
# after every resource of the resolver is on the queue, so its checkpoints follow them
|
|
108
|
+
self.checkpoints.resolver_finished(scope)
|
|
109
|
+
res.put(TableResolverFinished())
|
|
110
|
+
|
|
111
|
+
def _resolve_table(
|
|
112
|
+
self,
|
|
113
|
+
resolver: TableResolver,
|
|
114
|
+
depth: int,
|
|
115
|
+
client: Client,
|
|
116
|
+
parent_item: Optional[Resource],
|
|
117
|
+
res: queue.Queue,
|
|
118
|
+
scope: Scope,
|
|
119
|
+
):
|
|
120
|
+
total_resources = 0
|
|
121
|
+
try:
|
|
122
|
+
if depth == 0:
|
|
123
|
+
self._logger.info(
|
|
124
|
+
"table resolver started",
|
|
125
|
+
client_id=client.id(),
|
|
126
|
+
table=resolver.table.name,
|
|
127
|
+
depth=depth,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
self._logger.debug(
|
|
131
|
+
"table resolver started",
|
|
132
|
+
client_id=client.id(),
|
|
133
|
+
table=resolver.table.name,
|
|
134
|
+
depth=depth,
|
|
135
|
+
)
|
|
136
|
+
for item in resolver.resolve(client, parent_item):
|
|
137
|
+
try:
|
|
138
|
+
resource = self.resolve_resource(
|
|
139
|
+
resolver, client, parent_item, item
|
|
140
|
+
)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self._logger.error(
|
|
143
|
+
"failed to resolve resource",
|
|
144
|
+
client_id=client.id(),
|
|
145
|
+
table=resolver.table.name,
|
|
146
|
+
depth=depth,
|
|
147
|
+
exc_info=e,
|
|
148
|
+
)
|
|
149
|
+
self._logger.debug(
|
|
150
|
+
"details about resource that failed to resolve",
|
|
151
|
+
client_id=client.id(),
|
|
152
|
+
table=resolver.table.name,
|
|
153
|
+
resource=item,
|
|
154
|
+
)
|
|
155
|
+
continue
|
|
156
|
+
res.put(SyncInsertMessage(resource.to_arrow_record()))
|
|
157
|
+
for child_resolvers in resolver.child_resolvers:
|
|
158
|
+
res.put(TableResolverStarted())
|
|
159
|
+
self._pools[depth + 1].submit(
|
|
160
|
+
self.resolve_table,
|
|
161
|
+
child_resolvers,
|
|
162
|
+
depth + 1,
|
|
163
|
+
client,
|
|
164
|
+
resource,
|
|
165
|
+
res,
|
|
166
|
+
self.checkpoints.new_scope(scope),
|
|
167
|
+
)
|
|
168
|
+
total_resources += 1
|
|
169
|
+
if depth == 0:
|
|
170
|
+
self._logger.info(
|
|
171
|
+
"table resolver finished successfully",
|
|
172
|
+
client_id=client.id(),
|
|
173
|
+
table=resolver.table.name,
|
|
174
|
+
resources=total_resources,
|
|
175
|
+
depth=depth,
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
self._logger.debug(
|
|
179
|
+
"table resolver finished successfully",
|
|
180
|
+
client_id=client.id(),
|
|
181
|
+
table=resolver.table.name,
|
|
182
|
+
resources=total_resources,
|
|
183
|
+
depth=depth,
|
|
184
|
+
)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
self._logger.error(
|
|
187
|
+
"table resolver finished with error",
|
|
188
|
+
client_id=client.id(),
|
|
189
|
+
table=resolver.table.name,
|
|
190
|
+
resources=total_resources,
|
|
191
|
+
depth=depth,
|
|
192
|
+
exc_info=e,
|
|
193
|
+
)
|
|
194
|
+
res.put(SyncErrorMessage(resolver.table.name, str(e)))
|
|
195
|
+
|
|
196
|
+
def _sync(
|
|
197
|
+
self,
|
|
198
|
+
client,
|
|
199
|
+
resolvers: List[TableResolver],
|
|
200
|
+
res: queue.Queue,
|
|
201
|
+
deterministic_cq_id=False,
|
|
202
|
+
):
|
|
203
|
+
try:
|
|
204
|
+
for resolver in resolvers:
|
|
205
|
+
clients = resolver.multiplex(client)
|
|
206
|
+
for client in clients:
|
|
207
|
+
res.put(TableResolverStarted())
|
|
208
|
+
self._pools[0].submit(
|
|
209
|
+
self.resolve_table, resolver, 0, client, None, res
|
|
210
|
+
)
|
|
211
|
+
finally:
|
|
212
|
+
res.put(TableResolversSubmitted())
|
|
213
|
+
|
|
214
|
+
def sync(
|
|
215
|
+
self, client, resolvers: List[TableResolver], deterministic_cq_id=False
|
|
216
|
+
) -> Generator[SyncMessage, None, None]:
|
|
217
|
+
res = queue.Queue()
|
|
218
|
+
yield from self._send_migrate_table_messages(resolvers)
|
|
219
|
+
|
|
220
|
+
self.checkpoints.start(res)
|
|
221
|
+
try:
|
|
222
|
+
thread = futures.ThreadPoolExecutor()
|
|
223
|
+
thread.submit(self._sync, client, resolvers, res, deterministic_cq_id)
|
|
224
|
+
submitted = False
|
|
225
|
+
total_table_resolvers = 0
|
|
226
|
+
finished_table_resolvers = 0
|
|
227
|
+
while not (submitted and total_table_resolvers == finished_table_resolvers):
|
|
228
|
+
message = res.get()
|
|
229
|
+
if isinstance(message, TableResolversSubmitted):
|
|
230
|
+
submitted = True
|
|
231
|
+
elif isinstance(message, TableResolverStarted):
|
|
232
|
+
total_table_resolvers += 1
|
|
233
|
+
elif isinstance(message, TableResolverFinished):
|
|
234
|
+
finished_table_resolvers += 1
|
|
235
|
+
else:
|
|
236
|
+
yield message
|
|
237
|
+
|
|
238
|
+
self._post_sync_hook()
|
|
239
|
+
# checkpoints the post-sync hook flushed, after every resource
|
|
240
|
+
while not res.empty():
|
|
241
|
+
yield res.get_nowait()
|
|
242
|
+
thread.shutdown(wait=True)
|
|
243
|
+
finally:
|
|
244
|
+
self.checkpoints.stop()
|
|
245
|
+
|
|
246
|
+
def _send_migrate_table_messages(
|
|
247
|
+
self, resolvers: List[TableResolver]
|
|
248
|
+
) -> Generator[SyncMessage, None, None]:
|
|
249
|
+
for resolver in resolvers:
|
|
250
|
+
yield SyncMigrateTableMessage(table=resolver.table.to_arrow_schema())
|
|
251
|
+
if resolver.child_resolvers:
|
|
252
|
+
yield from self._send_migrate_table_messages(resolver.child_resolvers)
|
|
253
|
+
|
|
254
|
+
def set_post_sync_hook(self, fn):
|
|
255
|
+
"""
|
|
256
|
+
Use this to set a function that will be called after the sync is finished,
|
|
257
|
+
a la `defer fn()` in Go (but for a single function, rather than a stack).
|
|
258
|
+
|
|
259
|
+
This is necessary because plugins use this pattern on their sync method:
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
return self._scheduler.sync(...)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
So if a plugin has a `state_client`, there's nowhere to call the flush method.
|
|
266
|
+
"""
|
|
267
|
+
self._post_sync_hook = fn
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from cloudquery.sdk.schema.table import Table
|
|
2
|
+
from cloudquery.sdk.schema import Resource
|
|
3
|
+
from typing import Any, Generator, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Client:
|
|
7
|
+
def id(self) -> str:
|
|
8
|
+
raise NotImplementedError()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TableResolver:
|
|
12
|
+
def __init__(self, table: Table, child_resolvers: Optional[List] = None) -> None:
|
|
13
|
+
if child_resolvers is None:
|
|
14
|
+
child_resolvers = []
|
|
15
|
+
self._table = table
|
|
16
|
+
self._child_resolvers = child_resolvers
|
|
17
|
+
self.state_client = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def table(self) -> Table:
|
|
21
|
+
return self._table
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def child_resolvers(self):
|
|
25
|
+
return self._child_resolvers
|
|
26
|
+
|
|
27
|
+
def set_state_client(self, state_client):
|
|
28
|
+
self.state_client = state_client
|
|
29
|
+
|
|
30
|
+
def multiplex(self, client: Client) -> List[Client]:
|
|
31
|
+
return [client]
|
|
32
|
+
|
|
33
|
+
def resolve(self, client, parent_resource) -> Generator[Any, None, None]:
|
|
34
|
+
raise NotImplementedError()
|
|
35
|
+
|
|
36
|
+
def pre_resource_resolve(self, client: Client, resource):
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
def resolve_column(self, client: Client, resource: Resource, column_name: str):
|
|
40
|
+
if isinstance(resource.item, dict):
|
|
41
|
+
if column_name in resource.item:
|
|
42
|
+
resource.set(column_name, resource.item[column_name])
|
|
43
|
+
else:
|
|
44
|
+
if hasattr(resource.item, column_name):
|
|
45
|
+
resource.set(column_name, getattr(resource.item, column_name))
|
|
46
|
+
|
|
47
|
+
def post_resource_resolve(self, client: Client, resource):
|
|
48
|
+
return
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .column import Column
|
|
2
|
+
from .table import (
|
|
3
|
+
Table,
|
|
4
|
+
tables_to_arrow_schemas,
|
|
5
|
+
filter_dfs,
|
|
6
|
+
TableColumnChangeType,
|
|
7
|
+
TableColumnChange,
|
|
8
|
+
TableColumnChangeType,
|
|
9
|
+
get_table_changes,
|
|
10
|
+
get_table_column,
|
|
11
|
+
flatten_tables_recursive,
|
|
12
|
+
flatten_tables,
|
|
13
|
+
)
|
|
14
|
+
from .resource import Resource
|
|
15
|
+
|
|
16
|
+
# from .table_resolver import TableReso
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
METADATA_UNIQUE = b"cq:extension:unique"
|
|
2
|
+
METADATA_PRIMARY_KEY = b"cq:extension:primary_key"
|
|
3
|
+
METADATA_CONSTRAINT_NAME = b"cq:extension:constraint_name"
|
|
4
|
+
METADATA_INCREMENTAL = b"cq:extension:incremental"
|
|
5
|
+
|
|
6
|
+
METADATA_TRUE = b"true"
|
|
7
|
+
METADATA_FALSE = b"false"
|
|
8
|
+
METADATA_TABLE_NAME = b"cq:table_name"
|
|
9
|
+
METADATA_TABLE_DESCRIPTION = b"cq:table_description"
|
|
10
|
+
METADATA_TABLE_TITLE = b"cq:table_title"
|
|
11
|
+
METADATA_TABLE_DEPENDS_ON = b"cq:table_depends_on"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pyarrow as pa
|
|
4
|
+
|
|
5
|
+
from cloudquery.sdk.schema import arrow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Column:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
name: str,
|
|
12
|
+
type: pa.DataType,
|
|
13
|
+
description: str = "",
|
|
14
|
+
primary_key: bool = False,
|
|
15
|
+
not_null: bool = False,
|
|
16
|
+
incremental_key: bool = False,
|
|
17
|
+
unique: bool = False,
|
|
18
|
+
) -> None:
|
|
19
|
+
self.name = name
|
|
20
|
+
self.type = type
|
|
21
|
+
self.description = description
|
|
22
|
+
self.primary_key = primary_key
|
|
23
|
+
self.not_null = not_null
|
|
24
|
+
self.incremental_key = incremental_key
|
|
25
|
+
self.unique = unique
|
|
26
|
+
|
|
27
|
+
def __str__(self) -> str:
|
|
28
|
+
return f"Column(name={self.name}, type={self.type}, description={self.description}, primary_key={self.primary_key}, not_null={self.not_null}, incremental_key={self.incremental_key}, unique={self.unique})"
|
|
29
|
+
|
|
30
|
+
def __repr__(self) -> str:
|
|
31
|
+
return f"Column(name={self.name}, type={self.type}, description={self.description}, primary_key={self.primary_key}, not_null={self.not_null}, incremental_key={self.incremental_key}, unique={self.unique})"
|
|
32
|
+
|
|
33
|
+
def __eq__(self, __value: object) -> bool:
|
|
34
|
+
if isinstance(__value, Column):
|
|
35
|
+
return (
|
|
36
|
+
self.name == __value.name
|
|
37
|
+
and self.type == __value.type
|
|
38
|
+
and self.description == __value.description
|
|
39
|
+
and self.primary_key == __value.primary_key
|
|
40
|
+
and self.not_null == __value.not_null
|
|
41
|
+
and self.incremental_key == __value.incremental_key
|
|
42
|
+
and self.unique == __value.unique
|
|
43
|
+
)
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def to_arrow_field(self):
|
|
47
|
+
metadata = {
|
|
48
|
+
arrow.METADATA_PRIMARY_KEY: (
|
|
49
|
+
arrow.METADATA_TRUE if self.primary_key else arrow.METADATA_FALSE
|
|
50
|
+
),
|
|
51
|
+
arrow.METADATA_UNIQUE: (
|
|
52
|
+
arrow.METADATA_TRUE if self.unique else arrow.METADATA_FALSE
|
|
53
|
+
),
|
|
54
|
+
arrow.METADATA_INCREMENTAL: (
|
|
55
|
+
arrow.METADATA_TRUE if self.incremental_key else arrow.METADATA_FALSE
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
return pa.field(
|
|
59
|
+
self.name, self.type, metadata=metadata, nullable=not self.not_null
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def from_arrow_field(field: pa.Field) -> Column:
|
|
64
|
+
metadata = field.metadata or {}
|
|
65
|
+
primary_key = metadata.get(arrow.METADATA_PRIMARY_KEY) == arrow.METADATA_TRUE
|
|
66
|
+
unique = metadata.get(arrow.METADATA_UNIQUE) == arrow.METADATA_TRUE
|
|
67
|
+
incremental_key = (
|
|
68
|
+
metadata.get(arrow.METADATA_INCREMENTAL) == arrow.METADATA_TRUE
|
|
69
|
+
)
|
|
70
|
+
return Column(
|
|
71
|
+
field.name,
|
|
72
|
+
field.type,
|
|
73
|
+
primary_key=primary_key,
|
|
74
|
+
not_null=not field.nullable,
|
|
75
|
+
unique=unique,
|
|
76
|
+
incremental_key=incremental_key,
|
|
77
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .table import Table
|
|
2
|
+
from typing import Any
|
|
3
|
+
import pyarrow as pa
|
|
4
|
+
from cloudquery.sdk.scalar import ScalarFactory
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Resource:
|
|
8
|
+
def __init__(self, table: Table, parent, item: Any) -> None:
|
|
9
|
+
self._table = table
|
|
10
|
+
self._parent = parent
|
|
11
|
+
self._item = item
|
|
12
|
+
factory = ScalarFactory()
|
|
13
|
+
self._data = [factory.new_scalar(i.type) for i in table.columns]
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def item(self):
|
|
17
|
+
return self._item
|
|
18
|
+
|
|
19
|
+
def set(self, column_name: str, value: Any):
|
|
20
|
+
index_column = self._table.index_column(column_name)
|
|
21
|
+
self._data[index_column].set(value)
|
|
22
|
+
|
|
23
|
+
def to_list_of_arr(self):
|
|
24
|
+
return [[self._data[i].value] for i, _ in enumerate(self._table.columns)]
|
|
25
|
+
|
|
26
|
+
def to_arrow_record(self):
|
|
27
|
+
return pa.record_batch(
|
|
28
|
+
self.to_list_of_arr(), schema=self._table.to_arrow_schema()
|
|
29
|
+
)
|