cloudquery-plugin-sdk 0.1.55__tar.gz → 0.1.57__tar.gz

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 (63) hide show
  1. {cloudquery_plugin_sdk-0.1.55/cloudquery_plugin_sdk.egg-info → cloudquery_plugin_sdk-0.1.57}/PKG-INFO +2 -1
  2. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/checkpoint.py +61 -8
  3. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scheduler/scheduler.py +50 -35
  4. cloudquery_plugin_sdk-0.1.57/cloudquery/sdk/stateclient/__init__.py +2 -0
  5. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/stateclient/stateclient.py +31 -0
  6. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57/cloudquery_plugin_sdk.egg-info}/PKG-INFO +2 -1
  7. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery_plugin_sdk.egg-info/requires.txt +1 -0
  8. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/pyproject.toml +3 -2
  9. cloudquery_plugin_sdk-0.1.55/cloudquery/sdk/stateclient/__init__.py +0 -1
  10. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/LICENSE +0 -0
  11. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/README.md +0 -0
  12. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/__init__.py +0 -0
  13. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/__init__.py +0 -0
  14. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/memdb/__init__.py +0 -0
  15. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/memdb/memdb.py +0 -0
  16. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/servers/__init__.py +0 -0
  17. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/servers/discovery_v1/__init__.py +0 -0
  18. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/servers/discovery_v1/discovery.py +0 -0
  19. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/servers/plugin_v3/__init__.py +0 -0
  20. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/internal/servers/plugin_v3/plugin.py +0 -0
  21. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/message/__init__.py +0 -0
  22. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/message/read.py +0 -0
  23. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/message/sync.py +0 -0
  24. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/message/write.py +0 -0
  25. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/plugin/__init__.py +0 -0
  26. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/plugin/plugin.py +0 -0
  27. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/py.typed +0 -0
  28. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/__init__.py +0 -0
  29. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/binary.py +0 -0
  30. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/bool.py +0 -0
  31. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/date32.py +0 -0
  32. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/date64.py +0 -0
  33. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/float.py +0 -0
  34. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/int.py +0 -0
  35. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/json.py +0 -0
  36. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/list.py +0 -0
  37. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/scalar.py +0 -0
  38. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/scalar_factory.py +0 -0
  39. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/string.py +0 -0
  40. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/timestamp.py +0 -0
  41. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/uint.py +0 -0
  42. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/uuid.py +0 -0
  43. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scalar/vector.py +0 -0
  44. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scheduler/__init__.py +0 -0
  45. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/scheduler/table_resolver.py +0 -0
  46. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/schema/__init__.py +0 -0
  47. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/schema/arrow.py +0 -0
  48. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/schema/column.py +0 -0
  49. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/schema/resource.py +0 -0
  50. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/schema/table.py +0 -0
  51. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/serve/__init__.py +0 -0
  52. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/serve/plugin.py +0 -0
  53. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/stateclient/keys.py +0 -0
  54. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/transformers/__init__.py +0 -0
  55. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/transformers/openapi.py +0 -0
  56. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/transformers/transformers.py +0 -0
  57. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/types/__init__.py +0 -0
  58. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/types/json.py +0 -0
  59. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery/sdk/types/uuid.py +0 -0
  60. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery_plugin_sdk.egg-info/SOURCES.txt +0 -0
  61. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery_plugin_sdk.egg-info/dependency_links.txt +0 -0
  62. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/cloudquery_plugin_sdk.egg-info/top_level.txt +0 -0
  63. {cloudquery_plugin_sdk-0.1.55 → cloudquery_plugin_sdk-0.1.57}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudquery-plugin-sdk
3
- Version: 0.1.55
3
+ Version: 0.1.57
4
4
  Summary: CloudQuery Plugin SDK for Python
5
5
  Author-email: CloudQuery LTD <pypi-packages@cloudquery.io>
6
6
  License-Expression: MPL-2.0
@@ -16,6 +16,7 @@ Classifier: Topic :: Internet
16
16
  Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
+ Requires-Dist: certifi>=2026.10.19
19
20
  Requires-Dist: cloudquery-plugin-pb>=0.0.55
20
21
  Requires-Dist: grpcio>=1.78.0
21
22
  Requires-Dist: grpcio-tools>=1.78.0
@@ -22,6 +22,14 @@ from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple
22
22
  from cloudquery.sdk.message import SyncCheckpointMessage
23
23
 
24
24
 
25
+ class SyncNotRunningError(RuntimeError):
26
+ """
27
+ Raised when state checkpoints are flushed while their sync is not running, so they cannot be
28
+ sent: before or after Scheduler.sync, or from a resolver of an ended sync. Flush state that
29
+ summarizes the whole sync in the scheduler's post-sync hook instead.
30
+ """
31
+
32
+
25
33
  class Scope:
26
34
  """
27
35
  A table resolver call and the relations its resources spawn.
@@ -39,6 +47,10 @@ class Scope:
39
47
  self.resolving = True
40
48
  # checkpoints waiting for this scope to finish
41
49
  self.waiters: List[_Pending] = []
50
+ # set while a resource is resolved, until it is on the queue and its relations are submitted
51
+ self.resource_in_flight = False
52
+ # checkpoints waiting for that resource and its relations
53
+ self.after_resource: List[_Pending] = []
42
54
 
43
55
  def finished(self) -> bool:
44
56
  return not self.resolving and not self.open_children
@@ -112,6 +124,37 @@ class Checkpoints:
112
124
  """Returns the scope of the table resolver running in this context, or None outside table resolvers."""
113
125
  return self._scope.get()
114
126
 
127
+ @contextmanager
128
+ def resource(self, scope: Scope) -> Iterator[None]:
129
+ """
130
+ Marks a resource of scope as in flight until it is on the queue and its relations are
131
+ submitted, so that checkpoints flushed meanwhile, e.g. from its resource hooks, follow
132
+ the resource and its relations.
133
+ """
134
+ with self._lock:
135
+ scope.resource_in_flight = True
136
+ try:
137
+ yield
138
+ finally:
139
+ with self._lock:
140
+ scope.resource_in_flight = False
141
+ ready: List[_Pending] = []
142
+ for pending in scope.after_resource:
143
+ pending.blockers -= 1
144
+ self._wait_for_children(pending, scope)
145
+ if pending.blockers == 0:
146
+ ready.append(pending)
147
+ scope.after_resource = []
148
+ self._send(ready)
149
+
150
+ def superseded(self, scope: Optional[Scope]) -> bool:
151
+ """Returns True if scope belongs to a sync that ended while another sync runs now."""
152
+ return (
153
+ scope is not None
154
+ and self._sync is not None
155
+ and scope.sync is not self._sync
156
+ )
157
+
115
158
  def resolver_finished(self, scope: Scope) -> None:
116
159
  """
117
160
  Marks the resolver of scope as returned, once every resource it produced is on the queue.
@@ -143,17 +186,17 @@ class Checkpoints:
143
186
  wait for nothing. Schedules all of them or, if it raises, none of them.
144
187
 
145
188
  Raises:
146
- RuntimeError: if no sync is running, as the checkpoints could not be sent.
189
+ SyncNotRunningError: if the sync of the checkpoints is not running.
147
190
  """
148
191
  with self._lock:
149
192
  if self._res is None:
150
- raise RuntimeError(
193
+ raise SyncNotRunningError(
151
194
  "state checkpoints can only be flushed while the scheduler syncs, "
152
195
  "e.g. from a table resolver or the post-sync hook"
153
196
  )
154
197
  for scopes, _, _ in checkpoints:
155
- if any(s is not None and s.sync is not self._sync for s in scopes):
156
- raise RuntimeError(
198
+ if any(self.superseded(s) for s in scopes):
199
+ raise SyncNotRunningError(
157
200
  "state checkpoints of a table resolver can only be flushed during its own sync"
158
201
  )
159
202
  for scopes, keys, msg in checkpoints:
@@ -167,11 +210,11 @@ class Checkpoints:
167
210
  ) -> None:
168
211
  pending = _Pending(msg, keys, self._sync)
169
212
  for scope in {s for s in scopes if s is not None}:
170
- # the scope's own resources are on the queue already: resolvers put each
171
- # resource on it before they resume, so only its relations can be in flight
172
- for child in scope.open_children:
173
- child.waiters.append(pending)
213
+ if scope.resource_in_flight:
214
+ scope.after_resource.append(pending)
174
215
  pending.blockers += 1
216
+ else:
217
+ self._wait_for_children(pending, scope)
175
218
  earlier = set()
176
219
  for key in keys:
177
220
  previous = self._unsent.get(key)
@@ -183,6 +226,16 @@ class Checkpoints:
183
226
  if pending.blockers == 0:
184
227
  self._send([pending])
185
228
 
229
+ @staticmethod
230
+ def _wait_for_children(pending: _Pending, scope: Scope) -> None:
231
+ """
232
+ Makes pending wait for the relations of scope's resources. Its resources are on the queue
233
+ already, as none is in flight. Called with the lock held.
234
+ """
235
+ for child in scope.open_children:
236
+ child.waiters.append(pending)
237
+ pending.blockers += 1
238
+
186
239
  def _send(self, ready: List[_Pending]) -> None:
187
240
  """Puts ready checkpoints on the queue, then the ones that only waited for them. Called with the lock held."""
188
241
  while ready:
@@ -1,4 +1,5 @@
1
1
  import queue
2
+ import threading
2
3
  from concurrent import futures
3
4
  from typing import List, Generator, Any, Optional
4
5
 
@@ -19,9 +20,22 @@ QUEUE_PER_WORKER = 100
19
20
 
20
21
 
21
22
  class ThreadPoolExecutorWithQueueSizeLimit(futures.ThreadPoolExecutor):
22
- def __init__(self, maxsize, *args, **kwargs):
23
- super().__init__(*args, **kwargs)
24
- self._work_queue = queue.Queue(maxsize=maxsize)
23
+ def __init__(self, maxsize: int, max_workers: int, *args, **kwargs):
24
+ super().__init__(max_workers, *args, **kwargs)
25
+ self.maxsize = maxsize
26
+ # submit() puts on the work queue while holding a lock that every pool shares, so a
27
+ # full bounded queue would block the other pools' workers too: limit it before submit
28
+ self._slots = threading.BoundedSemaphore(maxsize + max_workers)
29
+
30
+ def submit(self, fn, /, *args, **kwargs):
31
+ self._slots.acquire()
32
+ try:
33
+ future = super().submit(fn, *args, **kwargs)
34
+ except BaseException:
35
+ self._slots.release()
36
+ raise
37
+ future.add_done_callback(lambda _: self._slots.release())
38
+ return future
25
39
 
26
40
 
27
41
  class TableResolverStarted:
@@ -61,7 +75,7 @@ class Scheduler:
61
75
  )
62
76
  self._pools: List[ThreadPoolExecutorWithQueueSizeLimit] = []
63
77
  current_depth_concurrency = concurrency
64
- current_depth_queue_size = queue_size
78
+ current_depth_queue_size = self._queue_size
65
79
  for _ in range(max_depth + 1):
66
80
  self._pools.append(
67
81
  ThreadPoolExecutorWithQueueSizeLimit(
@@ -134,37 +148,38 @@ class Scheduler:
134
148
  depth=depth,
135
149
  )
136
150
  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
- )
151
+ with self.checkpoints.resource(scope):
152
+ try:
153
+ resource = self.resolve_resource(
154
+ resolver, client, parent_item, item
155
+ )
156
+ except Exception as e:
157
+ self._logger.error(
158
+ "failed to resolve resource",
159
+ client_id=client.id(),
160
+ table=resolver.table.name,
161
+ depth=depth,
162
+ exc_info=e,
163
+ )
164
+ self._logger.debug(
165
+ "details about resource that failed to resolve",
166
+ client_id=client.id(),
167
+ table=resolver.table.name,
168
+ resource=item,
169
+ )
170
+ continue
171
+ res.put(SyncInsertMessage(resource.to_arrow_record()))
172
+ for child_resolvers in resolver.child_resolvers:
173
+ res.put(TableResolverStarted())
174
+ self._pools[depth + 1].submit(
175
+ self.resolve_table,
176
+ child_resolvers,
177
+ depth + 1,
178
+ client,
179
+ resource,
180
+ res,
181
+ self.checkpoints.new_scope(scope),
182
+ )
168
183
  total_resources += 1
169
184
  if depth == 0:
170
185
  self._logger.info(
@@ -0,0 +1,2 @@
1
+ from cloudquery.sdk.internal.checkpoint import SyncNotRunningError
2
+ from .keys import state_key
@@ -79,14 +79,22 @@ class StateClient(ABC):
79
79
 
80
80
  self.migrate_state_table()
81
81
  self.read_all_state()
82
+ self._flushed = dict(self.mem)
82
83
 
83
84
  def get_key(self, key: str) -> Optional[str]:
84
85
  with self._lock:
86
+ self._forget_changes_of_ended_syncs(key)
85
87
  return self.mem.get(key)
86
88
 
87
89
  def set_key(self, key: str, value: str) -> None:
88
90
  scope = self.checkpoints.current_scope() if self.checkpoints else None
91
+ if self.checkpoints and self.checkpoints.superseded(scope):
92
+ return
89
93
  with self._lock:
94
+ # with checkpoints, the value also covers the caller's resources, so later checkpoints
95
+ # of the key must still follow them even if it is unchanged
96
+ if self.checkpoints is None and self.mem.get(key) == value:
97
+ return
90
98
  self.mem[key] = value
91
99
  self.changes.setdefault(key, set()).add(scope)
92
100
 
@@ -95,9 +103,17 @@ class StateClient(ABC):
95
103
  Persists the keys changed since the last flush: writes them to the backend, or, with
96
104
  checkpoints, sends them on the sync stream after the resources they cover.
97
105
  If that fails, the keys stay changed, so a later flush retries them.
106
+ Changes made by table resolvers of an ended sync are dropped once another sync runs,
107
+ as the resources they cover may never have been committed.
108
+
109
+ Raises:
110
+ SyncNotRunningError: with checkpoints, if the sync is not running, e.g. once
111
+ Scheduler.sync returned. Flush in the scheduler's post-sync hook instead.
98
112
  """
99
113
  with self._flush_lock:
100
114
  with self._lock:
115
+ for key in list(self.changes):
116
+ self._forget_changes_of_ended_syncs(key)
101
117
  if not self.changes:
102
118
  return
103
119
  changes, self.changes = self.changes, {}
@@ -112,6 +128,21 @@ class StateClient(ABC):
112
128
  for key, scopes in changes.items():
113
129
  self.changes.setdefault(key, set()).update(scopes)
114
130
  raise
131
+ with self._lock:
132
+ self._flushed.update(values)
133
+
134
+ def _forget_changes_of_ended_syncs(self, key: str) -> None:
135
+ if self.checkpoints is None or key not in self.changes:
136
+ return
137
+ scopes = {s for s in self.changes[key] if not self.checkpoints.superseded(s)}
138
+ if scopes:
139
+ self.changes[key] = scopes
140
+ return
141
+ del self.changes[key]
142
+ if key in self._flushed:
143
+ self.mem[key] = self._flushed[key]
144
+ else:
145
+ self.mem.pop(key, None)
115
146
 
116
147
  def _send_checkpoints(
117
148
  self, changes: Dict[str, Set[Optional[Scope]]], values: Dict[str, str]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudquery-plugin-sdk
3
- Version: 0.1.55
3
+ Version: 0.1.57
4
4
  Summary: CloudQuery Plugin SDK for Python
5
5
  Author-email: CloudQuery LTD <pypi-packages@cloudquery.io>
6
6
  License-Expression: MPL-2.0
@@ -16,6 +16,7 @@ Classifier: Topic :: Internet
16
16
  Requires-Python: >=3.11
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
+ Requires-Dist: certifi>=2026.10.19
19
20
  Requires-Dist: cloudquery-plugin-pb>=0.0.55
20
21
  Requires-Dist: grpcio>=1.78.0
21
22
  Requires-Dist: grpcio-tools>=1.78.0
@@ -1,3 +1,4 @@
1
+ certifi>=2026.10.19
1
2
  cloudquery-plugin-pb>=0.0.55
2
3
  grpcio>=1.78.0
3
4
  grpcio-tools>=1.78.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cloudquery-plugin-sdk"
7
- version = "0.1.55"
7
+ version = "0.1.57"
8
8
  description = "CloudQuery Plugin SDK for Python"
9
9
  readme = "README.md"
10
10
  license = "MPL-2.0"
@@ -23,6 +23,7 @@ classifiers = [
23
23
  "Topic :: Internet",
24
24
  ]
25
25
  dependencies = [
26
+ "certifi>=2026.10.19",
26
27
  "cloudquery-plugin-pb>=0.0.55",
27
28
  "grpcio>=1.78.0",
28
29
  "grpcio-tools>=1.78.0",
@@ -61,4 +62,4 @@ python_files = "tests/*.py"
61
62
 
62
63
  [tool.uv]
63
64
  exclude-newer = "1 week"
64
- exclude-newer-package = { cloudquery-plugin-pb = false }
65
+ exclude-newer-package = { cloudquery-plugin-pb = false, certifi = false }
@@ -1 +0,0 @@
1
- from .keys import state_key