eventsourcing 9.3.5__py3-none-any.whl → 9.4.0a2__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.

Potentially problematic release.


This version of eventsourcing might be problematic. Click here for more details.

@@ -26,11 +26,14 @@ from eventsourcing.persistence import (
26
26
  IntegrityError,
27
27
  JSONTranscoder,
28
28
  Mapper,
29
+ Notification,
29
30
  ProcessRecorder,
30
31
  StoredEvent,
31
32
  Tracking,
33
+ TrackingRecorder,
32
34
  Transcoding,
33
35
  UUIDAsHex,
36
+ WaitInterruptedError,
34
37
  )
35
38
  from eventsourcing.utils import Environment, get_topic
36
39
 
@@ -206,6 +209,7 @@ class AggregateRecorderTestCase(TestCase, ABC):
206
209
 
207
210
  class ApplicationRecorderTestCase(TestCase, ABC):
208
211
  INITIAL_VERSION = 1
212
+ EXPECT_CONTIGUOUS_NOTIFICATION_IDS = True
209
213
 
210
214
  @abstractmethod
211
215
  def create_recorder(self) -> ApplicationRecorder:
@@ -215,26 +219,15 @@ class ApplicationRecorderTestCase(TestCase, ABC):
215
219
  # Construct the recorder.
216
220
  recorder = self.create_recorder()
217
221
 
218
- max_notification_id = recorder.max_notification_id()
219
-
220
222
  # Check notifications methods work when there aren't any.
223
+ self.assertEqual(len(recorder.select_notifications(start=None, limit=3)), 0)
221
224
  self.assertEqual(
222
- recorder.max_notification_id(),
223
- max_notification_id,
224
- )
225
- self.assertEqual(
226
- len(recorder.select_notifications(max_notification_id + 1, 3)),
227
- 0,
228
- )
229
- self.assertEqual(
230
- len(
231
- recorder.select_notifications(
232
- max_notification_id + 1, 3, topics=["topic1"]
233
- )
234
- ),
225
+ len(recorder.select_notifications(start=None, limit=3, topics=["topic1"])),
235
226
  0,
236
227
  )
237
228
 
229
+ self.assertIsNone(recorder.max_notification_id())
230
+
238
231
  # Write two stored events.
239
232
  originator_id1 = uuid4()
240
233
  originator_id2 = uuid4()
@@ -253,9 +246,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
253
246
  )
254
247
 
255
248
  notification_ids = recorder.insert_events([stored_event1, stored_event2])
256
- self.assertEqual(
257
- notification_ids, [max_notification_id + 1, max_notification_id + 2]
258
- )
249
+ self.assertEqual(notification_ids, [1, 2])
259
250
 
260
251
  # Store a third event.
261
252
  stored_event3 = StoredEvent(
@@ -265,7 +256,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
265
256
  state=b"state3",
266
257
  )
267
258
  notification_ids = recorder.insert_events([stored_event3])
268
- self.assertEqual(notification_ids, [max_notification_id + 3])
259
+ self.assertEqual(notification_ids, [3])
269
260
 
270
261
  stored_events1 = recorder.select_events(originator_id1)
271
262
  stored_events2 = recorder.select_events(originator_id2)
@@ -279,111 +270,158 @@ class ApplicationRecorderTestCase(TestCase, ABC):
279
270
  recorder.insert_events([stored_event3])
280
271
 
281
272
  sleep(1) # Added to make eventsourcing-axon tests work, perhaps not necessary.
282
- notifications = recorder.select_notifications(max_notification_id + 1, 3)
273
+ notifications = recorder.select_notifications(start=None, limit=10)
283
274
  self.assertEqual(len(notifications), 3)
284
- self.assertEqual(notifications[0].id, max_notification_id + 1)
275
+ self.assertEqual(notifications[0].id, 1)
285
276
  self.assertEqual(notifications[0].originator_id, originator_id1)
286
277
  self.assertEqual(notifications[0].topic, "topic1")
287
278
  self.assertEqual(notifications[0].state, b"state1")
288
- self.assertEqual(notifications[1].id, max_notification_id + 2)
279
+ self.assertEqual(notifications[1].id, 2)
289
280
  self.assertEqual(notifications[1].originator_id, originator_id1)
290
281
  self.assertEqual(notifications[1].topic, "topic2")
291
282
  self.assertEqual(notifications[1].state, b"state2")
292
- self.assertEqual(notifications[2].id, max_notification_id + 3)
283
+ self.assertEqual(notifications[2].id, 3)
293
284
  self.assertEqual(notifications[2].originator_id, originator_id2)
294
285
  self.assertEqual(notifications[2].topic, "topic3")
295
286
  self.assertEqual(notifications[2].state, b"state3")
296
287
 
297
- notifications = recorder.select_notifications(
298
- max_notification_id + 1, 3, topics=["topic1", "topic2", "topic3"]
299
- )
288
+ notifications = recorder.select_notifications(start=1, limit=10)
300
289
  self.assertEqual(len(notifications), 3)
301
- self.assertEqual(notifications[0].id, max_notification_id + 1)
290
+ self.assertEqual(notifications[0].id, 1)
302
291
  self.assertEqual(notifications[0].originator_id, originator_id1)
303
292
  self.assertEqual(notifications[0].topic, "topic1")
304
293
  self.assertEqual(notifications[0].state, b"state1")
305
- self.assertEqual(notifications[1].id, max_notification_id + 2)
294
+ self.assertEqual(notifications[1].id, 2)
306
295
  self.assertEqual(notifications[1].originator_id, originator_id1)
307
296
  self.assertEqual(notifications[1].topic, "topic2")
308
297
  self.assertEqual(notifications[1].state, b"state2")
309
- self.assertEqual(notifications[2].id, max_notification_id + 3)
298
+ self.assertEqual(notifications[2].id, 3)
310
299
  self.assertEqual(notifications[2].originator_id, originator_id2)
311
300
  self.assertEqual(notifications[2].topic, "topic3")
312
301
  self.assertEqual(notifications[2].state, b"state3")
313
302
 
314
- notifications = recorder.select_notifications(
315
- max_notification_id + 1, 3, topics=["topic1"]
316
- )
317
- self.assertEqual(len(notifications), 1)
318
- self.assertEqual(notifications[0].id, max_notification_id + 1)
303
+ notifications = recorder.select_notifications(start=None, stop=2, limit=10)
304
+ self.assertEqual(len(notifications), 2)
305
+ self.assertEqual(notifications[0].id, 1)
319
306
  self.assertEqual(notifications[0].originator_id, originator_id1)
320
307
  self.assertEqual(notifications[0].topic, "topic1")
321
308
  self.assertEqual(notifications[0].state, b"state1")
309
+ self.assertEqual(notifications[1].id, 2)
310
+ self.assertEqual(notifications[1].originator_id, originator_id1)
311
+ self.assertEqual(notifications[1].topic, "topic2")
312
+ self.assertEqual(notifications[1].state, b"state2")
322
313
 
323
314
  notifications = recorder.select_notifications(
324
- max_notification_id + 1, 3, topics=["topic2"]
315
+ start=1, limit=10, inclusive_of_start=False
325
316
  )
326
- self.assertEqual(len(notifications), 1)
327
- self.assertEqual(notifications[0].id, max_notification_id + 2)
317
+ self.assertEqual(len(notifications), 2)
318
+ self.assertEqual(notifications[0].id, 2)
328
319
  self.assertEqual(notifications[0].originator_id, originator_id1)
329
320
  self.assertEqual(notifications[0].topic, "topic2")
330
321
  self.assertEqual(notifications[0].state, b"state2")
322
+ self.assertEqual(notifications[1].id, 3)
323
+ self.assertEqual(notifications[1].originator_id, originator_id2)
324
+ self.assertEqual(notifications[1].topic, "topic3")
325
+ self.assertEqual(notifications[1].state, b"state3")
331
326
 
332
327
  notifications = recorder.select_notifications(
333
- max_notification_id + 1, 3, topics=["topic3"]
328
+ start=2, limit=10, inclusive_of_start=False
334
329
  )
335
330
  self.assertEqual(len(notifications), 1)
336
- self.assertEqual(notifications[0].id, max_notification_id + 3)
331
+ self.assertEqual(notifications[0].id, 3)
337
332
  self.assertEqual(notifications[0].originator_id, originator_id2)
338
333
  self.assertEqual(notifications[0].topic, "topic3")
339
334
  self.assertEqual(notifications[0].state, b"state3")
340
335
 
341
336
  notifications = recorder.select_notifications(
342
- max_notification_id + 1, 3, topics=["topic1", "topic3"]
337
+ start=None, limit=10, topics=["topic1", "topic2", "topic3"]
343
338
  )
339
+ self.assertEqual(len(notifications), 3)
340
+ self.assertEqual(notifications[0].id, 1)
341
+ self.assertEqual(notifications[0].originator_id, originator_id1)
342
+ self.assertEqual(notifications[0].topic, "topic1")
343
+ self.assertEqual(notifications[0].state, b"state1")
344
+ self.assertEqual(notifications[1].id, 2)
345
+ self.assertEqual(notifications[1].originator_id, originator_id1)
346
+ self.assertEqual(notifications[1].topic, "topic2")
347
+ self.assertEqual(notifications[1].state, b"state2")
348
+ self.assertEqual(notifications[2].id, 3)
349
+ self.assertEqual(notifications[2].originator_id, originator_id2)
350
+ self.assertEqual(notifications[2].topic, "topic3")
351
+ self.assertEqual(notifications[2].state, b"state3")
352
+
353
+ notifications = recorder.select_notifications(1, 10, topics=["topic1"])
354
+ self.assertEqual(len(notifications), 1)
355
+ self.assertEqual(notifications[0].id, 1)
356
+ self.assertEqual(notifications[0].originator_id, originator_id1)
357
+ self.assertEqual(notifications[0].topic, "topic1")
358
+ self.assertEqual(notifications[0].state, b"state1")
359
+
360
+ notifications = recorder.select_notifications(1, 3, topics=["topic2"])
361
+ self.assertEqual(len(notifications), 1)
362
+ self.assertEqual(notifications[0].id, 2)
363
+ self.assertEqual(notifications[0].originator_id, originator_id1)
364
+ self.assertEqual(notifications[0].topic, "topic2")
365
+ self.assertEqual(notifications[0].state, b"state2")
366
+
367
+ notifications = recorder.select_notifications(1, 3, topics=["topic3"])
368
+ self.assertEqual(len(notifications), 1)
369
+ self.assertEqual(notifications[0].id, 3)
370
+ self.assertEqual(notifications[0].originator_id, originator_id2)
371
+ self.assertEqual(notifications[0].topic, "topic3")
372
+ self.assertEqual(notifications[0].state, b"state3")
373
+
374
+ notifications = recorder.select_notifications(1, 3, topics=["topic1", "topic3"])
344
375
  self.assertEqual(len(notifications), 2)
345
- self.assertEqual(notifications[0].id, max_notification_id + 1)
376
+ self.assertEqual(notifications[0].id, 1)
346
377
  self.assertEqual(notifications[0].originator_id, originator_id1)
347
378
  self.assertEqual(notifications[0].topic, "topic1")
348
379
  self.assertEqual(notifications[0].state, b"state1")
349
- self.assertEqual(notifications[1].id, max_notification_id + 3)
380
+ self.assertEqual(notifications[1].id, 3)
350
381
  self.assertEqual(notifications[1].topic, "topic3")
351
382
  self.assertEqual(notifications[1].state, b"state3")
352
383
 
353
- self.assertEqual(
354
- recorder.max_notification_id(),
355
- max_notification_id + 3,
356
- )
384
+ self.assertEqual(recorder.max_notification_id(), 3)
385
+
386
+ # Check limit is working
387
+ notifications = recorder.select_notifications(None, 1)
388
+ self.assertEqual(len(notifications), 1)
389
+ self.assertEqual(notifications[0].id, 1)
357
390
 
358
- notifications = recorder.select_notifications(max_notification_id + 1, 1)
391
+ notifications = recorder.select_notifications(2, 1)
359
392
  self.assertEqual(len(notifications), 1)
360
- self.assertEqual(notifications[0].id, max_notification_id + 1)
393
+ self.assertEqual(notifications[0].id, 2)
361
394
 
362
- notifications = recorder.select_notifications(max_notification_id + 2, 1)
395
+ notifications = recorder.select_notifications(1, 1, inclusive_of_start=False)
363
396
  self.assertEqual(len(notifications), 1)
364
- self.assertEqual(notifications[0].id, max_notification_id + 2)
397
+ self.assertEqual(notifications[0].id, 2)
365
398
 
366
- notifications = recorder.select_notifications(max_notification_id + 2, 2)
399
+ notifications = recorder.select_notifications(2, 2)
367
400
  self.assertEqual(len(notifications), 2)
368
- self.assertEqual(notifications[0].id, max_notification_id + 2)
369
- self.assertEqual(notifications[1].id, max_notification_id + 3)
401
+ self.assertEqual(notifications[0].id, 2)
402
+ self.assertEqual(notifications[1].id, 3)
370
403
 
371
- notifications = recorder.select_notifications(max_notification_id + 3, 1)
404
+ notifications = recorder.select_notifications(3, 1)
372
405
  self.assertEqual(len(notifications), 1)
373
- self.assertEqual(notifications[0].id, max_notification_id + 3)
406
+ self.assertEqual(notifications[0].id, 3)
374
407
 
375
- notifications = recorder.select_notifications(
376
- start=max_notification_id + 2, limit=10, stop=max_notification_id + 2
377
- )
408
+ notifications = recorder.select_notifications(3, 1, inclusive_of_start=False)
409
+ self.assertEqual(len(notifications), 0)
410
+
411
+ notifications = recorder.select_notifications(start=2, limit=10, stop=2)
378
412
  self.assertEqual(len(notifications), 1)
379
- self.assertEqual(notifications[0].id, max_notification_id + 2)
413
+ self.assertEqual(notifications[0].id, 2)
414
+
415
+ notifications = recorder.select_notifications(start=1, limit=10, stop=2)
416
+ self.assertEqual(len(notifications), 2, len(notifications))
417
+ self.assertEqual(notifications[0].id, 1)
418
+ self.assertEqual(notifications[1].id, 2)
380
419
 
381
420
  notifications = recorder.select_notifications(
382
- start=max_notification_id + 1, limit=10, stop=max_notification_id + 2
421
+ start=1, limit=10, stop=2, inclusive_of_start=False
383
422
  )
384
- self.assertEqual(len(notifications), 2, len(notifications))
385
- self.assertEqual(notifications[0].id, max_notification_id + 1)
386
- self.assertEqual(notifications[1].id, max_notification_id + 2)
423
+ self.assertEqual(len(notifications), 1, len(notifications))
424
+ self.assertEqual(notifications[0].id, 2)
387
425
 
388
426
  def test_concurrent_no_conflicts(self) -> None:
389
427
  print(self)
@@ -430,7 +468,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
430
468
  try:
431
469
  recorder.insert_events(stored_events)
432
470
 
433
- except Exception as e: # pragma: nocover
471
+ except Exception as e: # pragma: no cover
434
472
  if errors:
435
473
  return
436
474
  ended = datetime.now()
@@ -451,7 +489,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
451
489
  while not stop_reading.is_set():
452
490
  try:
453
491
  recorder.select_notifications(0, 10)
454
- except Exception as e: # pragma: nocover
492
+ except Exception as e: # pragma: no cover
455
493
  errors.append(e)
456
494
  return
457
495
  # else:
@@ -466,22 +504,22 @@ class ApplicationRecorderTestCase(TestCase, ABC):
466
504
  with ThreadPoolExecutor(max_workers=num_writers) as executor:
467
505
  futures = []
468
506
  for _ in range(num_writes_per_writer):
469
- if errors: # pragma: nocover
507
+ if errors: # pragma: no cover
470
508
  break
471
509
  future = executor.submit(insert_events)
472
510
  futures.append(future)
473
511
  for future in futures:
474
- if errors: # pragma: nocover
512
+ if errors: # pragma: no cover
475
513
  break
476
514
  try:
477
515
  future.result()
478
- except Exception as e: # pragma: nocover
516
+ except Exception as e: # pragma: no cover
479
517
  errors.append(e)
480
518
  break
481
519
 
482
520
  stop_reading.set()
483
521
 
484
- if errors: # pragma: nocover
522
+ if errors: # pragma: no cover
485
523
  raise errors[0]
486
524
 
487
525
  for thread_id, thread_num in threads.items():
@@ -529,7 +567,7 @@ class ApplicationRecorderTestCase(TestCase, ABC):
529
567
  try:
530
568
  recorder.insert_events(stored_events)
531
569
 
532
- except Exception: # pragma: nocover
570
+ except Exception: # pragma: no cover
533
571
  errors_happened.set()
534
572
  tb = traceback.format_exc()
535
573
  print(tb)
@@ -555,10 +593,174 @@ class ApplicationRecorderTestCase(TestCase, ABC):
555
593
  rate = num_jobs * num_events / (ended - started).total_seconds()
556
594
  print(f"Rate: {rate:.0f} inserts per second")
557
595
 
596
+ def optional_test_insert_subscribe(self) -> None:
597
+
598
+ recorder = self.create_recorder()
599
+
600
+ # Get the max notification ID.
601
+ max_notification_id1 = recorder.max_notification_id()
602
+
603
+ # Write two stored events.
604
+ originator_id1 = uuid4()
605
+ originator_id2 = uuid4()
606
+
607
+ stored_event1 = StoredEvent(
608
+ originator_id=originator_id1,
609
+ originator_version=self.INITIAL_VERSION,
610
+ topic="topic1",
611
+ state=b"state1",
612
+ )
613
+ stored_event2 = StoredEvent(
614
+ originator_id=originator_id1,
615
+ originator_version=self.INITIAL_VERSION + 1,
616
+ topic="topic2",
617
+ state=b"state2",
618
+ )
619
+
620
+ notification_ids = recorder.insert_events([stored_event1, stored_event2])
621
+ if self.EXPECT_CONTIGUOUS_NOTIFICATION_IDS:
622
+ self.assertEqual(notification_ids, [1, 2])
623
+
624
+ # Get the max notification ID.
625
+ max_notification_id2 = recorder.max_notification_id()
626
+
627
+ # Start a subscription with default value for 'start'.
628
+ with recorder.subscribe() as subscription:
629
+
630
+ # Receive events from the subscription.
631
+ for _ in subscription:
632
+ break
633
+
634
+ # Start a subscription with None value for 'start'.
635
+ with recorder.subscribe(gt=None) as subscription:
636
+
637
+ # Receive events from the subscription.
638
+ for _ in subscription:
639
+ break
640
+
641
+ # Start a subscription with int value for 'start'.
642
+ with recorder.subscribe(gt=max_notification_id1) as subscription:
643
+
644
+ # Receive events from the subscription.
645
+ notifications: List[Notification] = []
646
+ for notification in subscription:
647
+ notifications.append(notification)
648
+ if len(notifications) == 2:
649
+ break
650
+
651
+ # Check the events we received are the ones that were written.
652
+ self.assertEqual(
653
+ stored_event1.originator_id, notifications[0].originator_id
654
+ )
655
+ self.assertEqual(
656
+ stored_event1.originator_version, notifications[0].originator_version
657
+ )
658
+ self.assertEqual(
659
+ stored_event2.originator_id, notifications[1].originator_id
660
+ )
661
+ self.assertEqual(
662
+ stored_event2.originator_version, notifications[1].originator_version
663
+ )
664
+ if self.EXPECT_CONTIGUOUS_NOTIFICATION_IDS:
665
+ self.assertEqual(1, notifications[0].id)
666
+ self.assertEqual(2, notifications[1].id)
667
+
668
+ # Store a third event.
669
+ stored_event3 = StoredEvent(
670
+ originator_id=originator_id2,
671
+ originator_version=self.INITIAL_VERSION,
672
+ topic="topic3",
673
+ state=b"state3",
674
+ )
675
+ notification_ids = recorder.insert_events([stored_event3])
676
+ if self.EXPECT_CONTIGUOUS_NOTIFICATION_IDS:
677
+ self.assertEqual(notification_ids, [3])
678
+
679
+ # Receive events from the subscription.
680
+ for notification in subscription:
681
+ notifications.append(notification)
682
+ if len(notifications) == 3:
683
+ break
684
+
685
+ # Check the events we received are the ones that were written.
686
+ self.assertEqual(
687
+ stored_event3.originator_id, notifications[2].originator_id
688
+ )
689
+ self.assertEqual(
690
+ stored_event3.originator_version, notifications[2].originator_version
691
+ )
692
+ if self.EXPECT_CONTIGUOUS_NOTIFICATION_IDS:
693
+ self.assertEqual(3, notifications[2].id)
694
+
695
+ # Start a subscription with int value for 'start'.
696
+ with recorder.subscribe(gt=max_notification_id2) as subscription:
697
+
698
+ # Receive events from the subscription.
699
+ notifications: List[Notification] = []
700
+ for notification in subscription:
701
+ notifications.append(notification)
702
+ if len(notifications) == 1:
703
+ break
704
+
705
+ # Check the events we received are the ones that were written.
706
+ self.assertEqual(
707
+ stored_event3.originator_id, notifications[0].originator_id
708
+ )
709
+
558
710
  def close_db_connection(self, *args: Any) -> None:
559
711
  """"""
560
712
 
561
713
 
714
+ class TrackingRecorderTestCase(TestCase, ABC):
715
+ @abstractmethod
716
+ def create_recorder(self) -> ProcessRecorder:
717
+ """"""
718
+
719
+ def test_insert_tracking(self):
720
+ tracking_recorder = self.create_recorder()
721
+
722
+ # Construct tracking objects.
723
+ tracking1 = Tracking(notification_id=21, application_name="upstream1")
724
+ tracking2 = Tracking(notification_id=22, application_name="upstream1")
725
+ tracking3 = Tracking(notification_id=21, application_name="upstream2")
726
+
727
+ # Insert tracking objects.
728
+ tracking_recorder.insert_tracking(tracking=tracking1)
729
+ tracking_recorder.insert_tracking(tracking=tracking2)
730
+ tracking_recorder.insert_tracking(tracking=tracking3)
731
+
732
+ # Fail to insert same tracking object twice.
733
+ with self.assertRaises(IntegrityError):
734
+ tracking_recorder.insert_tracking(tracking=tracking1)
735
+ with self.assertRaises(IntegrityError):
736
+ tracking_recorder.insert_tracking(tracking=tracking2)
737
+ with self.assertRaises(IntegrityError):
738
+ tracking_recorder.insert_tracking(tracking=tracking3)
739
+
740
+ # Get latest tracked position.
741
+ self.assertEqual(tracking_recorder.max_tracking_id("upstream1"), 22)
742
+ self.assertEqual(tracking_recorder.max_tracking_id("upstream2"), 21)
743
+ self.assertIsNone(tracking_recorder.max_tracking_id("upstream3"))
744
+
745
+ # Check if an event notification has been processed.
746
+ assert tracking_recorder.has_tracking_id("upstream1", 21)
747
+ assert tracking_recorder.has_tracking_id("upstream1", 22)
748
+ assert tracking_recorder.has_tracking_id("upstream2", 21)
749
+ assert not tracking_recorder.has_tracking_id("upstream2", 22)
750
+
751
+ def test_wait(self):
752
+ tracking_recorder = self.create_recorder()
753
+ tracking1 = Tracking(notification_id=21, application_name="upstream1")
754
+ tracking_recorder.insert_tracking(tracking=tracking1)
755
+ tracking_recorder.wait("upstream1", 21)
756
+ with self.assertRaises(TimeoutError):
757
+ tracking_recorder.wait("upstream1", 22, timeout=0.1)
758
+ with self.assertRaises(WaitInterruptedError):
759
+ interrupt = Event()
760
+ interrupt.set()
761
+ tracking_recorder.wait("upstream1", 22, interrupt=interrupt)
762
+
763
+
562
764
  class ProcessRecorderTestCase(TestCase, ABC):
563
765
  @abstractmethod
564
766
  def create_recorder(self) -> ProcessRecorder:
@@ -569,10 +771,7 @@ class ProcessRecorderTestCase(TestCase, ABC):
569
771
  recorder = self.create_recorder()
570
772
 
571
773
  # Get current position.
572
- self.assertEqual(
573
- recorder.max_tracking_id("upstream_app"),
574
- 0,
575
- )
774
+ self.assertIsNone(recorder.max_tracking_id("upstream_app"))
576
775
 
577
776
  # Write two stored events.
578
777
  originator_id1 = uuid4()
@@ -775,7 +974,9 @@ class NonInterleavingNotificationIDsBaseCase(ABC, TestCase):
775
974
 
776
975
  sleep(1) # Added to make eventsourcing-axon tests work, perhaps not necessary.
777
976
  notifications = recorder.select_notifications(
778
- start=max_notification_id + 1, limit=2 * self.insert_num
977
+ start=max_notification_id,
978
+ limit=2 * self.insert_num,
979
+ inclusive_of_start=False,
779
980
  )
780
981
  ids_for_sequence1 = [
781
982
  e.id for e in notifications if e.originator_id == originator1_id
@@ -827,6 +1028,14 @@ class InfrastructureFactoryTestCase(ABC, TestCase):
827
1028
  def expected_application_recorder_class(self):
828
1029
  pass
829
1030
 
1031
+ @abstractmethod
1032
+ def expected_tracking_recorder_class(self):
1033
+ pass
1034
+
1035
+ @abstractmethod
1036
+ def tracking_recorder_subclass(self):
1037
+ pass
1038
+
830
1039
  @abstractmethod
831
1040
  def expected_process_recorder_class(self):
832
1041
  pass
@@ -978,6 +1187,26 @@ class InfrastructureFactoryTestCase(ABC, TestCase):
978
1187
  recorder = self.factory.application_recorder()
979
1188
  self.assertEqual(type(recorder), self.expected_application_recorder_class())
980
1189
 
1190
+ def test_create_tracking_recorder(self):
1191
+ recorder = self.factory.tracking_recorder()
1192
+ self.assertEqual(type(recorder), self.expected_tracking_recorder_class())
1193
+ self.assertIsInstance(recorder, TrackingRecorder)
1194
+
1195
+ # Exercise code path where table is not created.
1196
+ self.env["CREATE_TABLE"] = "f"
1197
+ recorder = self.factory.tracking_recorder()
1198
+ self.assertEqual(type(recorder), self.expected_tracking_recorder_class())
1199
+
1200
+ # Exercise code path where tracking recorder class is specified as arg.
1201
+ subclass = self.tracking_recorder_subclass()
1202
+ recorder = self.factory.tracking_recorder(subclass)
1203
+ self.assertEqual(type(recorder), subclass)
1204
+
1205
+ # Exercise code path where tracking recorder class is specified as topic.
1206
+ self.factory.env[self.factory.TRACKING_RECORDER_TOPIC] = get_topic(subclass)
1207
+ recorder = self.factory.tracking_recorder()
1208
+ self.assertEqual(type(recorder), subclass)
1209
+
981
1210
  def test_create_process_recorder(self):
982
1211
  recorder = self.factory.process_recorder()
983
1212
  self.assertEqual(type(recorder), self.expected_process_recorder_class())
eventsourcing/utils.py CHANGED
@@ -22,7 +22,7 @@ from typing import (
22
22
  overload,
23
23
  )
24
24
 
25
- if TYPE_CHECKING: # pragma: nocover
25
+ if TYPE_CHECKING:
26
26
  from types import FunctionType, WrapperDescriptorType
27
27
 
28
28
 
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2023, John Bywater
3
+ Copyright (c) 2025, John Bywater
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eventsourcing
3
- Version: 9.3.5
3
+ Version: 9.4.0a2
4
4
  Summary: Event sourcing in Python
5
5
  Home-page: https://github.com/pyeventsourcing/eventsourcing
6
6
  License: BSD 3-Clause
7
7
  Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
8
8
  Author: John Bywater
9
9
  Author-email: john.bywater@appropriatesoftware.net
10
- Requires-Python: >=3.8,<4.0
11
- Classifier: Development Status :: 5 - Production/Stable
10
+ Requires-Python: >=3.8, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
11
+ Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Education
14
14
  Classifier: Intended Audience :: Science/Research
@@ -18,22 +18,24 @@ Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python
19
19
  Classifier: Programming Language :: Python :: 3
20
20
  Classifier: Programming Language :: Python :: 3.8
21
- Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
25
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.9
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Provides-Extra: crypto
28
+ Provides-Extra: cryptography
28
29
  Provides-Extra: postgres
29
30
  Requires-Dist: backports.zoneinfo ; python_version < "3.9"
30
- Requires-Dist: psycopg[c,pool] (<=3.2.99999) ; extra == "postgres"
31
- Requires-Dist: pycryptodome (>=3.21,<3.22) ; extra == "crypto"
31
+ Requires-Dist: cryptography (>=44.0,<44.1) ; extra == "cryptography"
32
+ Requires-Dist: psycopg[pool] (<=3.2.99999) ; extra == "postgres"
33
+ Requires-Dist: pycryptodome (>=3.22,<3.23) ; extra == "crypto"
32
34
  Requires-Dist: typing_extensions
33
35
  Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
34
36
  Description-Content-Type: text/markdown
35
37
 
36
- [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg?branch=main)](https://github.com/pyeventsourcing/eventsourcing/tree/main)
38
+ [![Build Status](https://github.com/pyeventsourcing/eventsourcing/actions/workflows/runtests.yaml/badge.svg)](https://github.com/pyeventsourcing/eventsourcing)
37
39
  [![Coverage Status](https://coveralls.io/repos/github/pyeventsourcing/eventsourcing/badge.svg?branch=main)](https://coveralls.io/github/pyeventsourcing/eventsourcing?branch=main)
38
40
  [![Documentation Status](https://readthedocs.org/projects/eventsourcing/badge/?version=stable)](https://eventsourcing.readthedocs.io/en/stable/)
39
41
  [![Latest Release](https://badge.fury.io/py/eventsourcing.svg)](https://pypi.org/project/eventsourcing/)
@@ -0,0 +1,26 @@
1
+ eventsourcing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ eventsourcing/application.py,sha256=eV_R00f5z7u6E5ou2-QpLJnJ2LzjGMxG1WP6Qm_mVfk,36214
3
+ eventsourcing/cipher.py,sha256=AjgOlOv9FF6xyXiFnHwcK6NX5IJ3nPHFL5GzIyWozyg,3265
4
+ eventsourcing/compressor.py,sha256=IdvrJUB9B2td871oifInv4lGXmHwYL9d69MbHHCr7uI,421
5
+ eventsourcing/cryptography.py,sha256=ZsQFyeyMZysADqKy38ECV71j6EMMSbo3VQO7oRnC1h0,2994
6
+ eventsourcing/dispatch.py,sha256=yYSpT-jqc6l_wTdqEnfPJJfvsZN2Ta8g2anrVPWIcqQ,1412
7
+ eventsourcing/domain.py,sha256=pNetJA4uKf1chgfNFKWv1Fke3_V5g0ygRv63WT7nsUc,58208
8
+ eventsourcing/interface.py,sha256=LIFI9AZhoVWUAq4YjKosGCpinf51jmVLqw1Ii4npSHo,5079
9
+ eventsourcing/persistence.py,sha256=h5laiUP4mc6Ur1PoDnNBLXRcl8V-2-ife_hYktlCmmM,45732
10
+ eventsourcing/popo.py,sha256=4q4-l8iuEueqvYzGMzyFEiZaT8dm_PJNosmGwzRt1oo,9393
11
+ eventsourcing/postgres.py,sha256=h4ETgjzZtj9a-otJkMfzHZSxD_hOUWyVTIVuESVI7y0,36138
12
+ eventsourcing/projection.py,sha256=6UNNF_iVYR3nmOfoysgvhtjwYnEBTvsHkQM2gzXPTbQ,6489
13
+ eventsourcing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ eventsourcing/sqlite.py,sha256=3bezfl3H9I366NnIwLpn4cz_L3fk0pHNiOSO_Hrq6Xw,21909
15
+ eventsourcing/system.py,sha256=7cM3FBdvr64ZK_xItks1MW2yg1o0OZ1vtKtznFFB_4g,47114
16
+ eventsourcing/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ eventsourcing/tests/application.py,sha256=Q_NFgRSRWovMcyQOZ8U1AoiIsnEHf_zCpHcP0ADaYvs,17369
18
+ eventsourcing/tests/domain.py,sha256=lHSlY6jIoSeqlcPSbrrozEPUJGvJ8bgPrznlmzTxn2w,3254
19
+ eventsourcing/tests/persistence.py,sha256=uink4W_CjeAlaSfzqRBuaDH2fYKAlZ1rvL7lhyUobp8,55553
20
+ eventsourcing/tests/postgres_utils.py,sha256=xymcGYasUXeZTBenkHz-ykD8HtrFjVM1Z7-qRrH6OQk,1364
21
+ eventsourcing/utils.py,sha256=QPlHhltgEcL80RWcPJ_PTygzDwhfIowUS3Z5taw0_cA,8228
22
+ eventsourcing-9.4.0a2.dist-info/AUTHORS,sha256=8aHOM4UbNZcKlD-cHpFRcM6RWyCqtwtxRev6DeUgVRs,137
23
+ eventsourcing-9.4.0a2.dist-info/LICENSE,sha256=CQEQzcZO8AWXL5i3hIo4yVKrYjh2FBz6hCM7kpXWpw4,1512
24
+ eventsourcing-9.4.0a2.dist-info/METADATA,sha256=NE7vnBA04-Huk9ZpUL8f3E-W2_BJhFm4V_uX7ZLvX_o,9874
25
+ eventsourcing-9.4.0a2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
26
+ eventsourcing-9.4.0a2.dist-info/RECORD,,