plexus-python-common 1.0.13__tar.gz → 1.0.14__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 (66) hide show
  1. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/ormutils.py +419 -0
  3. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  4. plexus_python_common-1.0.14/test/plexus_tests/common/utils/ormutils_test.py +572 -0
  5. plexus_python_common-1.0.13/test/plexus_tests/common/utils/ormutils_test.py +0 -112
  6. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/.editorconfig +0 -0
  7. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/.github/workflows/pr.yml +0 -0
  8. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/.github/workflows/push.yml +0 -0
  9. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/.gitignore +0 -0
  10. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/MANIFEST.in +0 -0
  11. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/README.md +0 -0
  12. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/VERSION +0 -0
  13. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/pyproject.toml +0 -0
  14. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  15. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  16. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  17. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/0-dummy +0 -0
  18. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/1-dummy +0 -0
  19. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/2-dummy +0 -0
  20. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.0.0.jsonl +0 -0
  21. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.0.0.vol-0.jsonl +0 -0
  22. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.0.jsonl +0 -0
  23. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.1.1.jsonl +0 -0
  24. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.1.1.vol-1.jsonl +0 -0
  25. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.1.jsonl +0 -0
  26. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.2.2.jsonl +0 -0
  27. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.2.2.vol-2.jsonl +0 -0
  28. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.2.jsonl +0 -0
  29. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.csv.part0 +0 -0
  30. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.csv.part1 +0 -0
  31. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.csv.part2 +0 -0
  32. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/resources/unittest/shutils/dummy.txt +0 -0
  33. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/setup.cfg +0 -0
  34. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/setup.py +0 -0
  35. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/__init__.py +0 -0
  36. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/carto/OSMFile.py +0 -0
  37. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/carto/OSMNode.py +0 -0
  38. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/carto/OSMTags.py +0 -0
  39. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/carto/OSMWay.py +0 -0
  40. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/carto/__init__.py +0 -0
  41. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/config.py +0 -0
  42. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/pose.py +0 -0
  43. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/proj.py +0 -0
  44. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/__init__.py +0 -0
  45. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/bagutils.py +0 -0
  46. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/datautils.py +0 -0
  47. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/jsonutils.py +0 -0
  48. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/s3utils.py +0 -0
  49. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/shutils.py +0 -0
  50. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus/common/utils/strutils.py +0 -0
  51. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
  52. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  53. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  54. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/requires.txt +0 -0
  55. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  56. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_test.py +0 -0
  57. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/__init__.py +0 -0
  58. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  59. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  60. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/pose_test.py +0 -0
  61. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/proj_test.py +0 -0
  62. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  63. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  64. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  65. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/utils/shutils_test.py +0 -0
  66. {plexus_python_common-1.0.13 → plexus_python_common-1.0.14}/test/plexus_tests/common/utils/strutils_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.13
3
+ Version: 1.0.14
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -4,6 +4,8 @@ from typing import Protocol, Self, TypeVar
4
4
  import pydantic as pdt
5
5
  import sqlalchemy as sa
6
6
  import sqlalchemy.dialects.postgresql as sa_pg
7
+ import sqlalchemy.exc as sa_exc
8
+ import sqlalchemy.orm as sa_orm
7
9
  from sqlmodel import Field, SQLModel
8
10
 
9
11
  from plexus.common.utils.datautils import validate_dt_timezone
@@ -30,7 +32,30 @@ __all__ = [
30
32
  "SerialModel",
31
33
  "RecordModel",
32
34
  "SnapshotModel",
35
+ "clone_serial_model_instance",
36
+ "clone_record_model_instance",
37
+ "clone_snapshot_model_instance",
33
38
  "make_snapshot_model_trigger",
39
+ "db_create_serial_model",
40
+ "db_create_serial_models",
41
+ "db_read_serial_model",
42
+ "db_read_serial_models",
43
+ "db_update_serial_model",
44
+ "db_delete_serial_model",
45
+ "db_create_record_model",
46
+ "db_create_record_models",
47
+ "db_update_record_model",
48
+ "db_create_snapshot_model",
49
+ "db_create_snapshot_models",
50
+ "db_read_snapshot_models_of_record",
51
+ "db_read_latest_snapshot_model_of_record",
52
+ "db_read_active_snapshot_model_of_record",
53
+ "db_read_expired_snapshot_models_of_record",
54
+ "db_read_latest_snapshot_models",
55
+ "db_read_active_snapshot_models",
56
+ "db_update_snapshot_model",
57
+ "db_expire_snapshot_model",
58
+ "db_activate_snapshot_model",
34
59
  ]
35
60
 
36
61
  ModelT = TypeVar("ModelT", bound=SQLModel)
@@ -363,7 +388,62 @@ class SnapshotModel(make_base_model(), make_snapshot_model_mixin(), table=True):
363
388
  pass
364
389
 
365
390
 
391
+ def clone_serial_model_instance(
392
+ model: type[SerialModelMixin],
393
+ instance: SerialModelMixin,
394
+ *,
395
+ clear_meta_fields: bool = True,
396
+ inplace: bool = False,
397
+ ) -> SerialModelMixin:
398
+ result = model.model_validate(instance)
399
+ result = instance if inplace else result
400
+ if clear_meta_fields:
401
+ result.sid = None
402
+ return result
403
+
404
+
405
+ def clone_record_model_instance(
406
+ model: type[RecordModelMixin],
407
+ instance: RecordModelMixin,
408
+ *,
409
+ clear_meta_fields: bool = True,
410
+ inplace: bool = False,
411
+ ) -> RecordModelMixin:
412
+ result = model.model_validate(instance)
413
+ result = instance if inplace else result
414
+ if clear_meta_fields:
415
+ result.sid = None
416
+ result.created_at = None
417
+ result.updated_at = None
418
+ return result
419
+
420
+
421
+ def clone_snapshot_model_instance(
422
+ model: type[SnapshotModelMixin],
423
+ instance: SnapshotModelMixin,
424
+ *,
425
+ clear_meta_fields: bool = True,
426
+ inplace: bool = False,
427
+ ) -> SnapshotModelMixin:
428
+ result = model.model_validate(instance)
429
+ result = instance if inplace else result
430
+ if clear_meta_fields:
431
+ result.sid = None
432
+ result.created_at = None
433
+ result.expired_at = None
434
+ result.record_sid = None
435
+ return result
436
+
437
+
366
438
  def make_snapshot_model_trigger(engine: sa.Engine, model: type[SQLModel]):
439
+ """
440
+ Creates the necessary database objects (sequence, function, trigger) to support automatic snapshot management
441
+ for the given snapshot model. This includes a sequence for `record_sid`, a function to handle snapshot updates,
442
+ and a trigger to invoke the function before inserts. The model must extend `SnapshotModel`.
443
+
444
+ :param engine: SQLAlchemy engine connected to the target database.
445
+ :param model: The snapshot model class extending `SnapshotModel`.
446
+ """
367
447
  table_name = model.__tablename__
368
448
  if not table_name:
369
449
  raise ValueError("missing '__tablename__' attribute")
@@ -418,3 +498,342 @@ def make_snapshot_model_trigger(engine: sa.Engine, model: type[SQLModel]):
418
498
  conn.execute(sa.text(create_snapshot_auto_update_function_sql))
419
499
  conn.execute(sa.text(create_snapshot_auto_update_trigger_sql))
420
500
  conn.commit()
501
+
502
+
503
+ def db_create_serial_model(
504
+ db: sa_orm.Session,
505
+ model: type[SerialModelMixin],
506
+ instance: SerialModelMixin,
507
+ ) -> SerialModelMixin:
508
+ db_instance = model.model_validate(instance)
509
+ db.add(db_instance)
510
+ db.commit()
511
+
512
+ return db_instance
513
+
514
+
515
+ def db_create_serial_models(
516
+ db: sa_orm.Session,
517
+ model: type[SerialModelMixin],
518
+ instances: list[SerialModelMixin],
519
+ ) -> list[SerialModelMixin]:
520
+ db_instances = [model.model_validate(instance) for instance in instances]
521
+ db.bulk_save_objects(db_instances, return_defaults=True)
522
+ db.commit()
523
+
524
+ return db_instances
525
+
526
+
527
+ def db_read_serial_model(
528
+ db: sa_orm.Session,
529
+ model: type[SerialModelMixin],
530
+ sid: int,
531
+ ) -> SerialModelMixin:
532
+ db_instance = db.query(model).where(model.sid == sid).one_or_none()
533
+ if db_instance is None:
534
+ raise sa_exc.NoResultFound(f"'{model}' of specified sid '{sid}' not found")
535
+
536
+ return db_instance
537
+
538
+
539
+ def db_read_serial_models(
540
+ db: sa_orm.Session,
541
+ model: type[SerialModelMixin],
542
+ skip: int,
543
+ limit: int,
544
+ ) -> list[SerialModelMixin]:
545
+ return db.query(model).order_by(model.sid).offset(skip).limit(limit).all()
546
+
547
+
548
+ def db_update_serial_model(
549
+ db: sa_orm.Session,
550
+ model: type[SerialModelMixin],
551
+ instance: SerialModelMixin,
552
+ ) -> SerialModelMixin:
553
+ db_instance = db.query(model).where(model.sid == instance.sid).one_or_none()
554
+ if db_instance is None:
555
+ raise sa_exc.NoResultFound(f"'{model}' of specified sid '{instance.sid}' not found")
556
+
557
+ db_instance = model_copy_from(db_instance, instance, exclude_none=True)
558
+ model.model_validate(db_instance)
559
+ db.commit()
560
+
561
+ return db_instance
562
+
563
+
564
+ def db_delete_serial_model(
565
+ db: sa_orm.Session,
566
+ model: type[SerialModelMixin],
567
+ sid: int,
568
+ ) -> SerialModelMixin:
569
+ db_instance = db.query(model).where(model.sid == sid).one_or_none()
570
+ if db_instance is None:
571
+ raise sa_exc.NoResultFound(f"'{model}' of specified sid '{sid}' not found")
572
+
573
+ db.delete(db_instance)
574
+ db.commit()
575
+
576
+ return db_instance
577
+
578
+
579
+ def db_create_record_model(
580
+ db: sa_orm.Session,
581
+ model: type[RecordModelMixin],
582
+ instance: RecordModelMixin,
583
+ created_at: datetime.datetime | None = None,
584
+ ) -> RecordModelMixin:
585
+ db_instance = clone_serial_model_instance(model, instance)
586
+ db_instance.created_at = created_at
587
+ db_instance.updated_at = created_at
588
+ db.add(db_instance)
589
+ db.commit()
590
+
591
+ return db_instance
592
+
593
+
594
+ def db_create_record_models(
595
+ db: sa_orm.Session,
596
+ model: type[RecordModelMixin],
597
+ instances: list[RecordModelMixin],
598
+ created_at: datetime.datetime | None = None,
599
+ ) -> list[RecordModelMixin]:
600
+ db_instances = [clone_serial_model_instance(model, instance) for instance in instances]
601
+ for db_instance in db_instances:
602
+ db_instance.created_at = created_at
603
+ db_instance.updated_at = created_at
604
+ db.bulk_save_objects(db_instances, return_defaults=True)
605
+ db.commit()
606
+
607
+ return db_instances
608
+
609
+
610
+ def db_update_record_model(
611
+ db: sa_orm.Session,
612
+ model: type[RecordModelMixin],
613
+ instance: RecordModelMixin,
614
+ sid: int,
615
+ updated_at: datetime.datetime,
616
+ ) -> RecordModelMixin:
617
+ db_instance = db.query(model).where(model.sid == sid).one_or_none()
618
+ if db_instance is None:
619
+ raise sa_exc.NoResultFound(f"'{model}' of specified sid '{sid}' not found")
620
+
621
+ db_instance = model_copy_from(db_instance, instance, exclude_none=True)
622
+ db_instance.updated_at = updated_at
623
+ db_instance = clone_serial_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
624
+ db.commit()
625
+
626
+ return db_instance
627
+
628
+
629
+ def db_create_snapshot_model(
630
+ db: sa_orm.Session,
631
+ model: type[SnapshotModelMixin],
632
+ instance: SnapshotModelMixin,
633
+ created_at: datetime.datetime,
634
+ ) -> SnapshotModelMixin:
635
+ db_instance = clone_snapshot_model_instance(model, instance)
636
+
637
+ db_instance.created_at = created_at
638
+ db_instance.expired_at = None
639
+ db.add(db_instance)
640
+ db.flush()
641
+
642
+ db_instance.record_sid = db_instance.sid
643
+ db.commit()
644
+
645
+ return db_instance
646
+
647
+
648
+ def db_create_snapshot_models(
649
+ db: sa_orm.Session,
650
+ model: type[SnapshotModelMixin],
651
+ instances: list[SnapshotModelMixin],
652
+ created_at: datetime.datetime,
653
+ ) -> list[SnapshotModelMixin]:
654
+ db_instances = [clone_snapshot_model_instance(model, instance) for instance in instances]
655
+ for db_instance in db_instances:
656
+ db_instance.created_at = created_at
657
+ db_instance.expired_at = None
658
+ db.bulk_save_objects(db_instances, return_defaults=True)
659
+ db.flush()
660
+
661
+ for db_instance in db_instances:
662
+ db_instance.record_sid = db_instance.sid
663
+ db.commit()
664
+
665
+ return db_instances
666
+
667
+
668
+ def db_read_snapshot_models_of_record(
669
+ db: sa_orm.Session,
670
+ model: type[SnapshotModelMixin],
671
+ record_sid: int,
672
+ ) -> list[SnapshotModelMixin]:
673
+ return (
674
+ db
675
+ .query(model)
676
+ .where(model.record_sid == record_sid)
677
+ .order_by(model.created_at.desc())
678
+ .all()
679
+ )
680
+
681
+
682
+ def db_read_latest_snapshot_model_of_record(
683
+ db: sa_orm.Session,
684
+ model: type[SnapshotModelMixin],
685
+ record_sid: int,
686
+ ) -> SnapshotModelMixin:
687
+ db_instance = (
688
+ db
689
+ .query(model)
690
+ .where(model.record_sid == record_sid)
691
+ .order_by(model.created_at.desc())
692
+ .first()
693
+ )
694
+ if db_instance is None:
695
+ raise sa_exc.NoResultFound(f"'{model}' of specified record_sid '{record_sid}' not found")
696
+
697
+ return db_instance
698
+
699
+
700
+ def db_read_active_snapshot_model_of_record(
701
+ db: sa_orm.Session,
702
+ model: type[SnapshotModelMixin],
703
+ record_sid: int,
704
+ ) -> SnapshotModelMixin:
705
+ db_instance = db.query(model).where(model.record_sid == record_sid, model.expired_at.is_(None)).one_or_none()
706
+ if db_instance is None:
707
+ raise sa_exc.NoResultFound(f"Active '{model}' of specified record_sid '{record_sid}' not found")
708
+
709
+ return db_instance
710
+
711
+
712
+ def db_read_expired_snapshot_models_of_record(
713
+ db: sa_orm.Session,
714
+ model: type[SnapshotModelMixin],
715
+ record_sid: int,
716
+ ) -> list[SnapshotModelMixin]:
717
+ return (
718
+ db
719
+ .query(model)
720
+ .where(model.record_sid == record_sid, model.expired_at.is_not(None))
721
+ .order_by(model.expired_at.desc())
722
+ .all()
723
+ )
724
+
725
+
726
+ def db_read_latest_snapshot_models(
727
+ db: sa_orm.Session,
728
+ model: type[SnapshotModelMixin],
729
+ skip: int,
730
+ limit: int,
731
+ ) -> list[SnapshotModelMixin]:
732
+ subquery = (
733
+ db
734
+ .query(model.record_sid,
735
+ sa.func.max(model.created_at).label("max_created_at"))
736
+ .group_by(model.record_sid)
737
+ .subquery()
738
+ )
739
+
740
+ return (
741
+ db
742
+ .query(model)
743
+ .join(subquery,
744
+ sa.and_(model.record_sid == subquery.c.record_sid, model.created_at == subquery.c.max_created_at))
745
+ .order_by(model.record_sid)
746
+ .offset(skip)
747
+ .limit(limit)
748
+ .all()
749
+ )
750
+
751
+
752
+ def db_read_active_snapshot_models(
753
+ db: sa_orm.Session,
754
+ model: type[SnapshotModelMixin],
755
+ skip: int,
756
+ limit: int,
757
+ ) -> list[SnapshotModelMixin]:
758
+ return db.query(model).where(model.expired_at.is_(None)).offset(skip).limit(limit).all()
759
+
760
+
761
+ def db_update_snapshot_model(
762
+ db: sa_orm.Session,
763
+ model: type[SnapshotModelMixin],
764
+ instance: SnapshotModelMixin,
765
+ record_sid: int,
766
+ updated_at: datetime.datetime,
767
+ ) -> SnapshotModelMixin:
768
+ db_instance = db.query(model).where(model.record_sid == record_sid, model.expired_at.is_(None)).one_or_none()
769
+ if db_instance is None:
770
+ raise sa_exc.NoResultFound(f"Active '{model}' of specified record_sid '{record_sid}' not found")
771
+
772
+ db_instance.expired_at = updated_at
773
+ db_instance = clone_snapshot_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
774
+ db.flush()
775
+
776
+ db_new_instance = clone_snapshot_model_instance(model, instance)
777
+ db_new_instance.record_sid = record_sid
778
+ db_new_instance.created_at = updated_at
779
+ db_new_instance.expired_at = None
780
+ db.add(db_new_instance)
781
+ db.commit()
782
+
783
+ return db_new_instance
784
+
785
+
786
+ def db_expire_snapshot_model(
787
+ db: sa_orm.Session,
788
+ model: type[SnapshotModelMixin],
789
+ record_sid: int,
790
+ updated_at: datetime.datetime,
791
+ ) -> SnapshotModelMixin:
792
+ db_instance = (
793
+ db
794
+ .query(model)
795
+ .where(model.record_sid == record_sid, model.expired_at.is_(None))
796
+ .one_or_none()
797
+ )
798
+ if db_instance is None:
799
+ raise sa_exc.NoResultFound(f"Active '{model}' of specified record_sid '{record_sid}' not found")
800
+
801
+ db_instance.expired_at = updated_at
802
+ db_instance = clone_snapshot_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
803
+ db.commit()
804
+
805
+ return db_instance
806
+
807
+
808
+ def db_activate_snapshot_model(
809
+ db: sa_orm.Session,
810
+ model: type[SnapshotModelMixin],
811
+ record_sid: int,
812
+ updated_at: datetime.datetime,
813
+ ) -> SnapshotModelMixin:
814
+ db_instance = db.query(model).where(model.record_sid == record_sid, model.expired_at.is_(None)).one_or_none()
815
+ if db_instance is not None:
816
+ raise sa_exc.MultipleResultsFound(f"Active '{model}' of specified record_sid '{record_sid}' already exists")
817
+
818
+ db_instance = (
819
+ db
820
+ .query(model)
821
+ .where(model.record_sid == record_sid, model.expired_at.is_not(None))
822
+ .order_by(model.expired_at.desc())
823
+ .first()
824
+ )
825
+ if db_instance is None:
826
+ raise sa_exc.NoResultFound(f"Expired '{model}' of specified record_sid '{record_sid}' not found")
827
+
828
+ db_new_instance = clone_snapshot_model_instance(model, db_instance)
829
+ db_new_instance.record_sid = record_sid
830
+ db_new_instance.created_at = db_instance.expired_at
831
+ db_new_instance.expired_at = updated_at
832
+
833
+ db_new_instance = clone_snapshot_model_instance(model, db_new_instance, clear_meta_fields=False, inplace=True)
834
+ db_new_instance.created_at = updated_at
835
+ db_new_instance.expired_at = None
836
+ db.add(db_new_instance)
837
+ db.commit()
838
+
839
+ return db_new_instance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.13
3
+ Version: 1.0.14
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12