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,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,3 @@
1
+ from .scheduler import Scheduler
2
+ from .table_resolver import TableResolver
3
+ from .table_resolver import Client
@@ -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
+ )