nucliadb-utils 6.1.0.post2473__py3-none-any.whl → 6.1.0.post2476__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.
nucliadb_utils/const.py CHANGED
@@ -30,7 +30,7 @@ class Streams:
30
30
 
31
31
  name = "nucliadb"
32
32
  subject = "ndb.consumer.{partition}"
33
- group = "nucliadb-{partition}"
33
+ group = "nucliadb-pull-{partition}"
34
34
 
35
35
  class INGEST_PROCESSED:
36
36
  """
@@ -40,7 +40,7 @@ class Streams:
40
40
 
41
41
  name = "nucliadb"
42
42
  subject = "ndb.consumer.processed"
43
- group = "nucliadb-processed"
43
+ group = "nucliadb-pull-processed"
44
44
 
45
45
  class INDEX:
46
46
  """
@@ -80,3 +80,4 @@ class Features:
80
80
  LOG_REQUEST_PAYLOADS = "nucliadb_log_request_payloads"
81
81
  IGNORE_EXTRACTED_IN_SEARCH = "nucliadb_ignore_extracted_in_search"
82
82
  NIDX_READS = "nucliadb_nidx_reads"
83
+ PULL_PROCESSED_CONSUMERS_DEPLOYED = "nucliadb_pull_processed_consumers_deployed"
@@ -69,6 +69,10 @@ DEFAULT_FLAG_DATA: dict[str, Any] = {
69
69
  "rollout": 0,
70
70
  "variants": {"environment": ["local"]},
71
71
  },
72
+ const.Features.PULL_PROCESSED_CONSUMERS_DEPLOYED: {
73
+ "rollout": 0,
74
+ "variants": {"environment": ["local"]},
75
+ },
72
76
  }
73
77
 
74
78
 
nucliadb_utils/nats.py CHANGED
@@ -104,6 +104,11 @@ class MessageProgressUpdater:
104
104
  class NatsConnectionManager:
105
105
  _nc: NATSClient
106
106
  _subscriptions: list[tuple[Subscription, Callable[[], Awaitable[None]]]]
107
+ _pull_subscriptions: list[
108
+ tuple[
109
+ JetStreamContext.PullSubscription, asyncio.Task, Callable[[], Awaitable[None]], asyncio.Event
110
+ ]
111
+ ]
107
112
  _unhealthy_timeout = 10 # needs to be unhealth for 10 seconds to be unhealthy and force exit
108
113
 
109
114
  def __init__(
@@ -117,6 +122,7 @@ class NatsConnectionManager:
117
122
  self._nats_servers = nats_servers
118
123
  self._nats_creds = nats_creds
119
124
  self._subscriptions = []
125
+ self._pull_subscriptions = []
120
126
  self._lock = asyncio.Lock()
121
127
  self._healthy = True
122
128
  self._last_unhealthy: Optional[float] = None
@@ -155,11 +161,26 @@ class NatsConnectionManager:
155
161
 
156
162
  async def finalize(self):
157
163
  async with self._lock:
164
+ # Finalize push subscriptions
158
165
  for sub, _ in self._subscriptions:
159
166
  try:
160
167
  await sub.drain()
161
168
  except nats.errors.ConnectionClosedError: # pragma: no cover
162
169
  pass
170
+ self._subscriptions = []
171
+
172
+ # Finalize pull subscriptions
173
+ for pull_sub, task, _, cancelled in self._pull_subscriptions:
174
+ cancelled.set()
175
+ task.cancel()
176
+ try:
177
+ await task
178
+ except asyncio.CancelledError:
179
+ pass
180
+ await pull_sub.unsubscribe()
181
+ self._pull_subscriptions = []
182
+
183
+ # Close the connection
163
184
  try:
164
185
  await asyncio.wait_for(self._nc.drain(), timeout=1)
165
186
  except (
@@ -168,7 +189,6 @@ class NatsConnectionManager:
168
189
  ): # pragma: no cover
169
190
  pass
170
191
  await self._nc.close()
171
- self._subscriptions = []
172
192
 
173
193
  async def disconnected_cb(self) -> None:
174
194
  logger.info("Disconnected from NATS!")
@@ -193,6 +213,26 @@ class NatsConnectionManager:
193
213
  # should force exit here to restart the service
194
214
  self._healthy = False
195
215
  raise
216
+
217
+ existing_pull_subs = self._pull_subscriptions
218
+ self._pull_subscriptions = []
219
+ for pull_sub, task, recon_callback, cancelled in existing_pull_subs:
220
+ cancelled.set()
221
+ task.cancel()
222
+ try:
223
+ await task
224
+ except asyncio.CancelledError:
225
+ pass
226
+ try:
227
+ await pull_sub.unsubscribe()
228
+ await recon_callback()
229
+ except Exception:
230
+ logger.exception(
231
+ f"Error resubscribing to pull subscription on {self._nc.connected_url.netloc}"
232
+ )
233
+ # should force exit here to restart the service
234
+ self._healthy = False
235
+ raise
196
236
  self._healthy = True
197
237
  self._last_unhealthy = None # reset the last unhealthy time
198
238
 
@@ -236,17 +276,67 @@ class NatsConnectionManager:
236
276
 
237
277
  return sub
238
278
 
239
- async def _remove_subscription(self, subscription: Subscription):
279
+ async def pull_subscribe(
280
+ self,
281
+ *,
282
+ subject: str,
283
+ stream: str,
284
+ cb: Callable[[Msg], Awaitable[None]],
285
+ subscription_lost_cb: Callable[[], Awaitable[None]],
286
+ durable: Optional[str] = None,
287
+ config: Optional[nats.js.api.ConsumerConfig] = None,
288
+ ) -> JetStreamContext.PullSubscription:
289
+ psub = await self.js.pull_subscribe(
290
+ subject,
291
+ durable=durable, # type: ignore
292
+ stream=stream,
293
+ config=config, # type: ignore
294
+ )
295
+
296
+ cancelled = asyncio.Event()
297
+
298
+ async def consume(psub: JetStreamContext.PullSubscription, subject: str):
299
+ while True:
300
+ if cancelled.is_set():
301
+ break
302
+ try:
303
+ messages = await psub.fetch(batch=1)
304
+ for message in messages:
305
+ await cb(message)
306
+ except asyncio.CancelledError:
307
+ # Handle task cancellation
308
+ logger.info("Pull subscription consume task cancelled", extra={"subject": subject})
309
+ break
310
+ except TimeoutError:
311
+ pass
312
+ except Exception:
313
+ logger.exception("Error in pull_subscribe task", extra={"subject": subject})
314
+
315
+ task = asyncio.create_task(consume(psub, subject), name=f"pull_subscribe_{subject}")
316
+ self._pull_subscriptions.append((psub, task, subscription_lost_cb, cancelled))
317
+ return psub
318
+
319
+ async def _remove_subscription(
320
+ self, subscription: Union[Subscription, JetStreamContext.PullSubscription]
321
+ ):
240
322
  async with self._lock:
241
- sub_index = None
242
323
  for index, (sub, _) in enumerate(self._subscriptions):
243
324
  if sub is not subscription:
244
325
  continue
245
- sub_index = index
246
- break
247
- if sub_index is not None:
248
- self._subscriptions.pop(sub_index)
326
+ self._subscriptions.pop(index)
327
+ return
328
+ for index, (psub, task, _, cancelled) in enumerate(self._pull_subscriptions):
329
+ if psub is not subscription:
330
+ continue
331
+ self._pull_subscriptions.pop(index)
332
+ cancelled.set()
333
+ task.cancel()
334
+ try:
335
+ await task
336
+ except asyncio.CancelledError:
337
+ pass
338
+ return
249
339
 
250
- async def unsubscribe(self, subscription: Subscription):
340
+ async def unsubscribe(self, subscription: Union[Subscription, JetStreamContext.PullSubscription]):
251
341
  await subscription.unsubscribe()
252
342
  await self._remove_subscription(subscription)
@@ -75,7 +75,7 @@ class GCS(BaseImage):
75
75
  @pytest.fixture(scope="session")
76
76
  def gcs():
77
77
  container = GCS()
78
- host, port = container.run()
78
+ _, port = container.run()
79
79
  public_api_url = f"http://172.17.0.1:{port}"
80
80
  yield public_api_url
81
81
  container.stop()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nucliadb_utils
3
- Version: 6.1.0.post2473
3
+ Version: 6.1.0.post2476
4
4
  Home-page: https://nuclia.com
5
5
  License: BSD
6
6
  Classifier: Development Status :: 4 - Beta
@@ -24,8 +24,8 @@ Requires-Dist: PyNaCl
24
24
  Requires-Dist: pyjwt>=2.4.0
25
25
  Requires-Dist: memorylru>=1.1.2
26
26
  Requires-Dist: mrflagly>=0.2.9
27
- Requires-Dist: nucliadb-protos>=6.1.0.post2473
28
- Requires-Dist: nucliadb-telemetry>=6.1.0.post2473
27
+ Requires-Dist: nucliadb-protos>=6.1.0.post2476
28
+ Requires-Dist: nucliadb-telemetry>=6.1.0.post2476
29
29
  Provides-Extra: cache
30
30
  Requires-Dist: redis>=4.3.4; extra == "cache"
31
31
  Requires-Dist: orjson>=3.6.7; extra == "cache"
@@ -1,14 +1,14 @@
1
1
  nucliadb_utils/__init__.py,sha256=EvBCH1iTODe-AgXm48aj4kVUt_Std3PeL8QnwimR5wI,895
2
2
  nucliadb_utils/asyncio_utils.py,sha256=h8Y-xpcFFRgNzaiIW0eidz7griAQa7ggbNk34-tAt2c,2888
3
3
  nucliadb_utils/authentication.py,sha256=N__d2Ez3JHJv5asYK5TgUcIkKqcAC8ZTLlnfLhfSneM,5837
4
- nucliadb_utils/const.py,sha256=_UhW7A3VC0EG-OQDse4maZdP8hfsI_NQrLfBkonEFtA,2477
4
+ nucliadb_utils/const.py,sha256=0zsA5Ys2NQoJECenjX8aSgQGqizqmsUZCcIp87E-61A,2572
5
5
  nucliadb_utils/debug.py,sha256=Q56Nx9Dp7V2ae3CU2H0ztaZcHTJXdlflPLKLeOPZ170,2436
6
6
  nucliadb_utils/exceptions.py,sha256=y_3wk77WLVUtdo-5FtbBsdSkCtK_DsJkdWb5BoPn3qo,1094
7
- nucliadb_utils/featureflagging.py,sha256=dKp8u4g52KTcupKFJkdGYk3Z2pGQdi_q7IaoY_GQ0nE,2953
7
+ nucliadb_utils/featureflagging.py,sha256=5J5Je_cZ05f0xnKLvWDMLi3eqKGKAOfR_MItDnAHJj0,3086
8
8
  nucliadb_utils/grpc.py,sha256=apu0uePnkGHCAT7GRQ9YZfRYyFj26kJ440i8jitbM3U,3314
9
9
  nucliadb_utils/helpers.py,sha256=nPw8yod3hP-pxq80VF8QC36s7ygSg0dBUdfI-LatvCs,1600
10
10
  nucliadb_utils/indexing.py,sha256=Luaqcar3CySpdYOFp6Q9Fyr8ZYwhYhaKRHQ_VGL78f8,2318
11
- nucliadb_utils/nats.py,sha256=prs98dIrGh2-WchQcRBloc5wYhpjEnB0VtCERPnoReg,8430
11
+ nucliadb_utils/nats.py,sha256=SGBWtP0h3_9Ne6VEfZ2OLnWlEvzfAj63U252FrTITjY,11870
12
12
  nucliadb_utils/partition.py,sha256=jBgy4Hu5Iwn4gjbPPcthSykwf-qNx-GcLAIwbzPd1d0,1157
13
13
  nucliadb_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  nucliadb_utils/run.py,sha256=Es0_Bu5Yc-LWczvwL6gzWqSwC85RjDCk-0oFQAJi9g4,1827
@@ -54,13 +54,13 @@ nucliadb_utils/tests/__init__.py,sha256=Oo9CAE7B0eW5VHn8sHd6o30SQzOWUhktLPRXdlDO
54
54
  nucliadb_utils/tests/asyncbenchmark.py,sha256=x4be2IwCawle9zWgMOJkmwoUwk5p1tv7cLQGmybkEOg,10587
55
55
  nucliadb_utils/tests/azure.py,sha256=Dg-Eb4KVScG-O6P9y-bVQZTAKTNUMQ0i-CKEd9IdrWw,4474
56
56
  nucliadb_utils/tests/fixtures.py,sha256=i0sqPqe5a5JlKGFdaIvOlHYkZ3pHZ2hTIgTsaIB3vSM,3472
57
- nucliadb_utils/tests/gcs.py,sha256=weTGkuPyAbOzUwlXC4VHGQMJG1iKlsny842yJ8wslhk,4502
57
+ nucliadb_utils/tests/gcs.py,sha256=wO4xRhl-_wrddxlGyEktqa_bXHeQarQN-l_A72Tzr9A,4499
58
58
  nucliadb_utils/tests/indexing.py,sha256=YW2QhkhO9Q_8A4kKWJaWSvXvyQ_AiAwY1VylcfVQFxk,1513
59
59
  nucliadb_utils/tests/local.py,sha256=fXIBasrvdaFJM-sw2wk1_oiFzBcm9O10iCyC-OiXwY8,1914
60
60
  nucliadb_utils/tests/nats.py,sha256=xqpww4jZjTKY9oPGlJdDJG67L3FIBQsa9qDHxILR8r8,7687
61
61
  nucliadb_utils/tests/s3.py,sha256=pl-RJFjA4MH6iXkqhsh5g8gDuEhrYu1nPZ-laxlrMlE,3704
62
- nucliadb_utils-6.1.0.post2473.dist-info/METADATA,sha256=A2nR8tUEDZEnLS67mG611J17y1csF5DOHz9dHkJmymw,2055
63
- nucliadb_utils-6.1.0.post2473.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
64
- nucliadb_utils-6.1.0.post2473.dist-info/top_level.txt,sha256=fE3vJtALTfgh7bcAWcNhcfXkNPp_eVVpbKK-2IYua3E,15
65
- nucliadb_utils-6.1.0.post2473.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
66
- nucliadb_utils-6.1.0.post2473.dist-info/RECORD,,
62
+ nucliadb_utils-6.1.0.post2476.dist-info/METADATA,sha256=9xT2in9NaLwz4F16XWog7Kjcx3Udzqz3UlCHKN0dH1U,2055
63
+ nucliadb_utils-6.1.0.post2476.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
64
+ nucliadb_utils-6.1.0.post2476.dist-info/top_level.txt,sha256=fE3vJtALTfgh7bcAWcNhcfXkNPp_eVVpbKK-2IYua3E,15
65
+ nucliadb_utils-6.1.0.post2476.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
66
+ nucliadb_utils-6.1.0.post2476.dist-info/RECORD,,