plexus-python-common 1.0.68__py3-none-any.whl → 1.0.70__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.
@@ -1092,12 +1092,12 @@ def db_read_sequence_models[SequenceModelT: SequenceModelMixin](
1092
1092
  limit: int | None = None,
1093
1093
  order_by: list[str] | None = None,
1094
1094
  ) -> list[SequenceModelT]:
1095
- query = db.query(model).order_by(*db_make_order_by_clause(model, order_by))
1095
+ query_stmt = db.query(model).order_by(*db_make_order_by_clause(model, order_by))
1096
1096
  if skip is not None:
1097
- query = query.offset(skip)
1097
+ query_stmt = query_stmt.offset(skip)
1098
1098
  if limit is not None:
1099
- query = query.limit(limit)
1100
- return query.all()
1099
+ query_stmt = query_stmt.limit(limit)
1100
+ return query_stmt.all()
1101
1101
 
1102
1102
 
1103
1103
  def db_update_sequence_model[SequenceModelT: SequenceModelMixin](
@@ -1285,7 +1285,7 @@ def db_read_latest_snapshot_models[SnapshotModelT: SnapshotModelMixin](
1285
1285
  limit: int | None = None,
1286
1286
  order_by: list[str] | None = None,
1287
1287
  ) -> list[SnapshotModelT]:
1288
- subquery = (
1288
+ subquery_stmt = (
1289
1289
  db
1290
1290
  .query(model.record_sqn,
1291
1291
  sa.func.max(model.created_at).label("max_created_at"))
@@ -1293,18 +1293,19 @@ def db_read_latest_snapshot_models[SnapshotModelT: SnapshotModelMixin](
1293
1293
  .subquery()
1294
1294
  )
1295
1295
 
1296
- query = (
1296
+ query_stmt = (
1297
1297
  db
1298
1298
  .query(model)
1299
- .join(subquery,
1300
- sa.and_(model.record_sqn == subquery.c.record_sqn, model.created_at == subquery.c.max_created_at))
1299
+ .join(subquery_stmt,
1300
+ sa.and_(model.record_sqn == subquery_stmt.c.record_sqn,
1301
+ model.created_at == subquery_stmt.c.max_created_at))
1301
1302
  .order_by(*db_make_order_by_clause(model, order_by))
1302
1303
  )
1303
1304
  if skip is not None:
1304
- query = query.offset(skip)
1305
+ query_stmt = query_stmt.offset(skip)
1305
1306
  if limit is not None:
1306
- query = query.limit(limit)
1307
- return query.all()
1307
+ query_stmt = query_stmt.limit(limit)
1308
+ return query_stmt.all()
1308
1309
 
1309
1310
 
1310
1311
  def db_read_active_snapshot_models[SnapshotModelT: SnapshotModelMixin](
@@ -1314,12 +1315,12 @@ def db_read_active_snapshot_models[SnapshotModelT: SnapshotModelMixin](
1314
1315
  limit: int | None = None,
1315
1316
  order_by: list[str] | None = None,
1316
1317
  ) -> list[SnapshotModelT]:
1317
- query = db.query(model).where(model.expired_at.is_(None)).order_by(*db_make_order_by_clause(model, order_by))
1318
+ query_stmt = db.query(model).where(model.expired_at.is_(None)).order_by(*db_make_order_by_clause(model, order_by))
1318
1319
  if skip is not None:
1319
- query = query.offset(skip)
1320
+ query_stmt = query_stmt.offset(skip)
1320
1321
  if limit is not None:
1321
- query = query.limit(limit)
1322
- return query.all()
1322
+ query_stmt = query_stmt.limit(limit)
1323
+ return query_stmt.all()
1323
1324
 
1324
1325
 
1325
1326
  def db_update_snapshot_model[SnapshotModelT: SnapshotModelMixin](
@@ -1512,7 +1513,7 @@ def db_read_latest_revision_models[RevisionModelT: RevisionModelMixin](
1512
1513
  limit: int | None = None,
1513
1514
  order_by: list[str] | None = None,
1514
1515
  ) -> list[RevisionModelT]:
1515
- subquery = (
1516
+ subquery_stmt = (
1516
1517
  db
1517
1518
  .query(model.record_sqn,
1518
1519
  sa.func.max(model.revision).label("max_revision"))
@@ -1520,18 +1521,19 @@ def db_read_latest_revision_models[RevisionModelT: RevisionModelMixin](
1520
1521
  .subquery()
1521
1522
  )
1522
1523
 
1523
- query = (
1524
+ query_stmt = (
1524
1525
  db
1525
1526
  .query(model)
1526
- .join(subquery,
1527
- sa.and_(model.record_sqn == subquery.c.record_sqn, model.revision == subquery.c.max_revision))
1527
+ .join(subquery_stmt,
1528
+ sa.and_(model.record_sqn == subquery_stmt.c.record_sqn,
1529
+ model.revision == subquery_stmt.c.max_revision))
1528
1530
  .order_by(*db_make_order_by_clause(model, order_by))
1529
1531
  )
1530
1532
  if skip is not None:
1531
- query = query.offset(skip)
1533
+ query_stmt = query_stmt.offset(skip)
1532
1534
  if limit is not None:
1533
- query = query.limit(limit)
1534
- return query.all()
1535
+ query_stmt = query_stmt.limit(limit)
1536
+ return query_stmt.all()
1535
1537
 
1536
1538
 
1537
1539
  def db_read_active_revision_models[RevisionModelT: RevisionModelMixin](
@@ -1541,12 +1543,12 @@ def db_read_active_revision_models[RevisionModelT: RevisionModelMixin](
1541
1543
  limit: int | None = None,
1542
1544
  order_by: list[str] | None = None,
1543
1545
  ) -> list[RevisionModelT]:
1544
- query = db.query(model).where(model.expired_at.is_(None)).order_by(*db_make_order_by_clause(model, order_by))
1546
+ query_stmt = db.query(model).where(model.expired_at.is_(None)).order_by(*db_make_order_by_clause(model, order_by))
1545
1547
  if skip is not None:
1546
- query = query.offset(skip)
1548
+ query_stmt = query_stmt.offset(skip)
1547
1549
  if limit is not None:
1548
- query = query.limit(limit)
1549
- return query.all()
1550
+ query_stmt = query_stmt.limit(limit)
1551
+ return query_stmt.all()
1550
1552
 
1551
1553
 
1552
1554
  def db_update_revision_model[RevisionModelT: RevisionModelMixin](
@@ -394,15 +394,20 @@ def make_tag_target_model_mixin() -> type[pdt.BaseModel]:
394
394
  )
395
395
  vehicle_name: str = Field(
396
396
  sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=False),
397
- description="Vehicle name associated with the tag record",
397
+ description="Name of the tagger's applicability vehicle",
398
398
  )
399
399
  begin_dt: datetime.datetime = Field(
400
400
  sa_column=sa.Column(SQLiteDateTime, nullable=False),
401
- description="Begin datetime of the target range associated with the tag record",
401
+ description="Beginning of the tagger's applicability time range",
402
402
  )
403
403
  end_dt: datetime.datetime = Field(
404
404
  sa_column=sa.Column(SQLiteDateTime, nullable=False),
405
- description="End datetime of the target range associated with the tag record",
405
+ description="End of the tagger's applicability time range",
406
+ )
407
+ props: JsonType | None = Field(
408
+ sa_column=sa.Column(sa_sqlite.JSON, nullable=True),
409
+ default=None,
410
+ description="Additional properties of the tag target in JSON format",
406
411
  )
407
412
 
408
413
  @pdt.field_validator("identifier", mode="after")
@@ -414,7 +419,7 @@ def make_tag_target_model_mixin() -> type[pdt.BaseModel]:
414
419
  @pdt.field_validator("tagger_name", mode="after")
415
420
  @classmethod
416
421
  def validate_tagger_name(cls, v: str) -> str:
417
- validate_snake_case(v)
422
+ validate_slash_tag(v)
418
423
  return v
419
424
 
420
425
  @pdt.field_validator("tagger_version", mode="after")
@@ -466,21 +471,21 @@ def make_tag_record_model_mixin() -> type[pdt.BaseModel]:
466
471
  )
467
472
  begin_dt: datetime.datetime = Field(
468
473
  sa_column=sa.Column(SQLiteDateTime, nullable=False),
469
- description="Begin datetime of the tag record",
474
+ description="Begin of the tag record time range",
470
475
  )
471
476
  end_dt: datetime.datetime = Field(
472
477
  sa_column=sa.Column(SQLiteDateTime, nullable=False),
473
- description="End datetime of the tag record",
478
+ description="End of the tag record time range",
474
479
  )
475
480
  tagset_namespace: str | None = Field(
476
481
  sa_column=sa.Column(sa_sqlite.VARCHAR(64), nullable=True),
477
482
  default=None,
478
- description="Namespace of the tagset that the tag belongs to",
483
+ description="Namespace of the tagset that the tag in this record belongs to",
479
484
  )
480
485
  tagset_version: str | None = Field(
481
486
  sa_column=sa.Column(sa_sqlite.VARCHAR(32), nullable=True),
482
487
  default=None,
483
- description="Version of the tagset that the tag belongs to",
488
+ description="Version of the tagset that the tag in this record belongs to",
484
489
  )
485
490
  tag: str = Field(
486
491
  sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False),
@@ -573,6 +578,7 @@ if typing.TYPE_CHECKING:
573
578
  vehicle_name: sa_orm.Mapped[str] = ...
574
579
  begin_dt: sa_orm.Mapped[datetime.datetime] = ...
575
580
  end_dt: sa_orm.Mapped[datetime.datetime] = ...
581
+ props: sa_orm.Mapped[JsonType | None] = ...
576
582
 
577
583
 
578
584
  class TagTargetTable(TagTarget, SequenceModelMixinProtocol):
@@ -645,21 +651,21 @@ class TagCache(object):
645
651
  end_dt: datetime.datetime | None = None,
646
652
  ) -> list[TagTargetTable]:
647
653
  with self.make_session() as session:
648
- query = session.query(TagTargetTable)
654
+ query_stmt = session.query(TagTargetTable)
649
655
  if identifier:
650
- query = query.filter(TagTargetTable.identifier == identifier)
656
+ query_stmt = query_stmt.filter(TagTargetTable.identifier == identifier)
651
657
  if tagger_name:
652
- query = query.filter(TagTargetTable.tagger_name == tagger_name)
658
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_name == tagger_name)
653
659
  if tagger_version:
654
- query = query.filter(TagTargetTable.tagger_version == tagger_version)
660
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_version == tagger_version)
655
661
  if vehicle_name:
656
- query = query.filter(TagTargetTable.vehicle_name == vehicle_name)
662
+ query_stmt = query_stmt.filter(TagTargetTable.vehicle_name == vehicle_name)
657
663
  if begin_dt:
658
- query = query.filter(TagTargetTable.end_dt >= begin_dt)
664
+ query_stmt = query_stmt.filter(TagTargetTable.end_dt >= begin_dt)
659
665
  if end_dt:
660
- query = query.filter(TagTargetTable.begin_dt <= end_dt)
666
+ query_stmt = query_stmt.filter(TagTargetTable.begin_dt <= end_dt)
661
667
 
662
- return query.all()
668
+ return query_stmt.all()
663
669
 
664
670
  def add_target(
665
671
  self,
@@ -669,18 +675,20 @@ class TagCache(object):
669
675
  vehicle_name: str,
670
676
  begin_dt: datetime.datetime,
671
677
  end_dt: datetime.datetime,
678
+ props: JsonType | None = None,
672
679
  ) -> TagTargetTable:
673
680
  with self.make_session() as session:
674
- target_info = TagTarget(
681
+ tag_target = TagTarget(
675
682
  identifier=identifier,
676
683
  tagger_name=tagger_name,
677
684
  tagger_version=tagger_version,
678
685
  vehicle_name=vehicle_name,
679
686
  begin_dt=begin_dt,
680
687
  end_dt=end_dt,
688
+ props=props,
681
689
  )
682
- db_target_info = clone_sequence_model_instance(TagTargetTable, target_info)
683
- session.add(db_target_info)
690
+ db_tag_target = clone_sequence_model_instance(TagTargetTable, tag_target)
691
+ session.add(db_tag_target)
684
692
  session.commit()
685
693
 
686
694
  return self.get_target(identifier)
@@ -695,21 +703,21 @@ class TagCache(object):
695
703
  end_dt: datetime.datetime | None = None,
696
704
  ):
697
705
  with self.make_session() as session:
698
- query = session.query(TagTargetTable)
706
+ query_stmt = session.query(TagTargetTable)
699
707
  if identifier:
700
- query = query.filter(TagTargetTable.identifier == identifier)
708
+ query_stmt = query_stmt.filter(TagTargetTable.identifier == identifier)
701
709
  if tagger_name:
702
- query = query.filter(TagTargetTable.tagger_name == tagger_name)
710
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_name == tagger_name)
703
711
  if tagger_version:
704
- query = query.filter(TagTargetTable.tagger_version == tagger_version)
712
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_version == tagger_version)
705
713
  if vehicle_name:
706
- query = query.filter(TagTargetTable.vehicle_name == vehicle_name)
714
+ query_stmt = query_stmt.filter(TagTargetTable.vehicle_name == vehicle_name)
707
715
  if begin_dt:
708
- query = query.filter(TagTargetTable.end_dt >= begin_dt)
716
+ query_stmt = query_stmt.filter(TagTargetTable.end_dt >= begin_dt)
709
717
  if end_dt:
710
- query = query.filter(TagTargetTable.begin_dt <= end_dt)
718
+ query_stmt = query_stmt.filter(TagTargetTable.begin_dt <= end_dt)
711
719
 
712
- query.delete()
720
+ query_stmt.delete()
713
721
  session.commit()
714
722
 
715
723
  (
@@ -721,12 +729,16 @@ class TagCache(object):
721
729
  session.commit()
722
730
 
723
731
  def with_target(self, target: int | str) -> "TargetedTagCache":
724
- target_info = self.get_target(target)
725
- if target_info is None:
726
- raise ValueError(f"target '{target}' not found in cache")
727
- return TargetedTagCache(cache=self, target_info=target_info)
732
+ db_tag_target = self.get_target(target)
733
+ if db_tag_target is None:
734
+ raise ValueError(f"tag target '{target}' not found in cache")
735
+ return TargetedTagCache(cache=self, tag_target=db_tag_target)
736
+
737
+ def count_records(self) -> int:
738
+ with self.make_session() as session:
739
+ return session.query(sa.func.count(TagRecordTable.sqn)).scalar()
728
740
 
729
- def iter_tags(
741
+ def iter_records(
730
742
  self,
731
743
  begin_dt: datetime.datetime | None = None,
732
744
  end_dt: datetime.datetime | None = None,
@@ -736,6 +748,8 @@ class TagCache(object):
736
748
  *,
737
749
  tagsets: Sequence[Tagset] | None = None,
738
750
  tagset_inverted: bool = False,
751
+ skip: int | None = None,
752
+ limit: int | None = None,
739
753
  batch_size: int = 1000,
740
754
  ) -> Generator[TagRecordTable, None, None]:
741
755
  """
@@ -749,33 +763,41 @@ class TagCache(object):
749
763
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
750
764
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
751
765
  tagsets)
766
+ :param skip: Number of records to skip (for pagination)
767
+ :param limit: Maximum number of records to return (for pagination)
752
768
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
753
769
  :return: Generator of ``TagRecordTable`` instances that match the filters
754
770
  """
755
771
  with self.make_session() as session:
756
- query = session.query(TagRecordTable)
772
+ query_stmt = session.query(TagRecordTable)
757
773
  if begin_dt:
758
- query = query.filter(TagRecordTable.end_dt >= begin_dt)
774
+ query_stmt = query_stmt.filter(TagRecordTable.end_dt >= begin_dt)
759
775
  if end_dt:
760
- query = query.filter(TagRecordTable.begin_dt <= end_dt)
776
+ query_stmt = query_stmt.filter(TagRecordTable.begin_dt <= end_dt)
761
777
  if tagset_namespace:
762
- query = query.filter(TagRecordTable.tagset_namespace == tagset_namespace)
778
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_namespace == tagset_namespace)
763
779
  if tagset_version:
764
- query = query.filter(TagRecordTable.tagset_version == tagset_version)
780
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_version == tagset_version)
765
781
  if tag_prefix:
766
- query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
782
+ query_stmt = query_stmt.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
767
783
  if tagsets:
768
784
  if tagset_inverted:
769
- query = query.filter(
785
+ query_stmt = query_stmt.filter(
770
786
  TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
771
787
  else:
772
- query = query.filter(
788
+ query_stmt = query_stmt.filter(
773
789
  TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
774
-
775
- for result in query.yield_per(batch_size):
790
+ if skip is not None or limit is not None:
791
+ query_stmt = query_stmt.order_by(TagRecordTable.sqn.desc())
792
+ if skip is not None:
793
+ query_stmt = query_stmt.offset(skip)
794
+ if limit is not None:
795
+ query_stmt = query_stmt.limit(limit)
796
+
797
+ for result in query_stmt.yield_per(batch_size):
776
798
  yield result
777
799
 
778
- def iter_tag_and_targets(
800
+ def iter_record_and_targets(
779
801
  self,
780
802
  begin_dt: datetime.datetime | None = None,
781
803
  end_dt: datetime.datetime | None = None,
@@ -791,10 +813,12 @@ class TagCache(object):
791
813
  target_end_dt: datetime.datetime | None = None,
792
814
  tagsets: Sequence[Tagset] | None = None,
793
815
  tagset_inverted: bool = False,
816
+ skip: int | None = None,
817
+ limit: int | None = None,
794
818
  batch_size: int = 1000,
795
819
  ) -> Generator[tuple[TagRecordTable, TagTargetTable], None, None]:
796
820
  """
797
- Query tag records along with their target info in the cache with optional filters.
821
+ Query tag records along with their target in the cache with optional filters.
798
822
 
799
823
  :param begin_dt: Filter by begin time (inclusive)
800
824
  :param end_dt: Filter by end time (inclusive)
@@ -810,49 +834,57 @@ class TagCache(object):
810
834
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
811
835
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
812
836
  tagsets)
837
+ :param skip: Number of records to skip (for pagination)
838
+ :param limit: Maximum number of records to return (for pagination)
813
839
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
814
840
  :return: Generator of ``TagRecordTable`` instances that match the filters
815
841
  """
816
842
  with self.make_session() as session:
817
- query = (
843
+ query_stmt = (
818
844
  session
819
845
  .query(TagRecordTable, TagTargetTable)
820
846
  .join(TagTargetTable, TagRecordTable.target_sqn == TagTargetTable.sqn)
821
847
  )
822
848
  if begin_dt:
823
- query = query.filter(TagRecordTable.end_dt >= begin_dt)
849
+ query_stmt = query_stmt.filter(TagRecordTable.end_dt >= begin_dt)
824
850
  if end_dt:
825
- query = query.filter(TagRecordTable.begin_dt <= end_dt)
851
+ query_stmt = query_stmt.filter(TagRecordTable.begin_dt <= end_dt)
826
852
  if tagset_namespace:
827
- query = query.filter(TagRecordTable.tagset_namespace == tagset_namespace)
853
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_namespace == tagset_namespace)
828
854
  if tagset_version:
829
- query = query.filter(TagRecordTable.tagset_version == tagset_version)
855
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_version == tagset_version)
830
856
  if tag_prefix:
831
- query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
857
+ query_stmt = query_stmt.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
832
858
  if target_identifier:
833
- query = query.filter(TagTargetTable.identifier == target_identifier)
859
+ query_stmt = query_stmt.filter(TagTargetTable.identifier == target_identifier)
834
860
  if target_tagger_name:
835
- query = query.filter(TagTargetTable.tagger_name == target_tagger_name)
861
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_name == target_tagger_name)
836
862
  if target_tagger_version:
837
- query = query.filter(TagTargetTable.tagger_version == target_tagger_version)
863
+ query_stmt = query_stmt.filter(TagTargetTable.tagger_version == target_tagger_version)
838
864
  if target_vehicle_name:
839
- query = query.filter(TagTargetTable.vehicle_name == target_vehicle_name)
865
+ query_stmt = query_stmt.filter(TagTargetTable.vehicle_name == target_vehicle_name)
840
866
  if target_begin_dt:
841
- query = query.filter(TagTargetTable.end_dt >= target_begin_dt)
867
+ query_stmt = query_stmt.filter(TagTargetTable.end_dt >= target_begin_dt)
842
868
  if target_end_dt:
843
- query = query.filter(TagTargetTable.begin_dt <= target_end_dt)
869
+ query_stmt = query_stmt.filter(TagTargetTable.begin_dt <= target_end_dt)
844
870
  if tagsets:
845
871
  if tagset_inverted:
846
- query = query.filter(
872
+ query_stmt = query_stmt.filter(
847
873
  TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
848
874
  else:
849
- query = query.filter(
875
+ query_stmt = query_stmt.filter(
850
876
  TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
851
-
852
- for result in query.yield_per(batch_size):
877
+ if skip is not None or limit is not None:
878
+ query_stmt = query_stmt.order_by(TagRecordTable.sqn.desc())
879
+ if skip is not None:
880
+ query_stmt = query_stmt.offset(skip)
881
+ if limit is not None:
882
+ query_stmt = query_stmt.limit(limit)
883
+
884
+ for result in query_stmt.yield_per(batch_size):
853
885
  yield result
854
886
 
855
- def remove_tags(
887
+ def remove_records(
856
888
  self,
857
889
  begin_dt: datetime.datetime | None = None,
858
890
  end_dt: datetime.datetime | None = None,
@@ -876,26 +908,26 @@ class TagCache(object):
876
908
  tagsets)
877
909
  """
878
910
  with self.make_session() as session:
879
- query = session.query(TagRecordTable)
911
+ query_stmt = session.query(TagRecordTable)
880
912
  if begin_dt:
881
- query = query.filter(TagRecordTable.end_dt >= begin_dt)
913
+ query_stmt = query_stmt.filter(TagRecordTable.end_dt >= begin_dt)
882
914
  if end_dt:
883
- query = query.filter(TagRecordTable.begin_dt <= end_dt)
915
+ query_stmt = query_stmt.filter(TagRecordTable.begin_dt <= end_dt)
884
916
  if tagset_namespace:
885
- query = query.filter(TagRecordTable.tagset_namespace == tagset_namespace)
917
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_namespace == tagset_namespace)
886
918
  if tagset_version:
887
- query = query.filter(TagRecordTable.tagset_version == tagset_version)
919
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_version == tagset_version)
888
920
  if tag_prefix:
889
- query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
921
+ query_stmt = query_stmt.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
890
922
  if tagsets:
891
923
  if tagset_inverted:
892
- query = query.filter(
924
+ query_stmt = query_stmt.filter(
893
925
  TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
894
926
  else:
895
- query = query.filter(
927
+ query_stmt = query_stmt.filter(
896
928
  TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
897
929
 
898
- query.delete()
930
+ query_stmt.delete()
899
931
  session.commit()
900
932
 
901
933
  def clear(self):
@@ -924,42 +956,59 @@ class TagCache(object):
924
956
  return
925
957
 
926
958
  with src.make_session() as src_session, dst.make_session() as dst_session:
927
- src_targets = src_session.query(TagTargetTable).all()
928
- dst_targets = [
929
- clone_sequence_model_instance(TagTargetTable, db_tag_target_info, clear_meta_fields=True)
930
- for db_tag_target_info in src_targets
959
+ src_db_tag_targets = src_session.query(TagTargetTable).all()
960
+ dst_db_tag_targets = [
961
+ clone_sequence_model_instance(TagTargetTable, db_tag_target, clear_meta_fields=True)
962
+ for db_tag_target in src_db_tag_targets
931
963
  ]
932
- dst_session.add_all(dst_targets)
964
+ dst_session.add_all(dst_db_tag_targets)
933
965
  dst_session.flush() # ensure new sqn values are assigned
934
966
 
935
- sqn_map = {src_target.sqn: dst_target.sqn for src_target, dst_target in zip(src_targets, dst_targets)}
967
+ sqn_map = {src_db_tag_target.sqn: dst_db_tag_target.sqn
968
+ for src_db_tag_target, dst_db_tag_target in zip(src_db_tag_targets, dst_db_tag_targets)}
936
969
 
937
- for results in batched(src_session.query(TagRecordTable).yield_per(1000), 1000):
938
- clones = []
939
- for db_tag_record in results:
940
- cloned = clone_sequence_model_instance(TagRecordTable, db_tag_record, clear_meta_fields=True)
970
+ for db_tag_records in batched(src_session.query(TagRecordTable).yield_per(1000), 1000):
971
+ cloned_db_tag_records = []
972
+ for db_tag_record in db_tag_records:
973
+ cloned_db_tag_record = clone_sequence_model_instance(TagRecordTable,
974
+ db_tag_record,
975
+ clear_meta_fields=True)
941
976
  try:
942
- cloned.target_sqn = sqn_map[db_tag_record.target_sqn]
977
+ cloned_db_tag_record.target_sqn = sqn_map[db_tag_record.target_sqn]
943
978
  except KeyError as e:
944
- raise ValueError(f"no cloned target for target_sqn '{db_tag_record.target_sqn}'") from e
945
- clones.append(cloned)
946
- dst_session.add_all(clones)
979
+ raise ValueError(f"no cloned tag target for target_sqn '{db_tag_record.target_sqn}'") from e
980
+ cloned_db_tag_records.append(cloned_db_tag_record)
981
+ dst_session.add_all(cloned_db_tag_records)
947
982
  dst_session.commit()
948
983
 
984
+ count_tags = count_records
985
+ iter_tags = iter_records
986
+ iter_tag_and_targets = iter_record_and_targets
987
+ remove_tags = remove_records
988
+
949
989
 
950
990
  class TargetedTagCache(object):
951
- def __init__(self, cache: TagCache, target_info: TagTargetTable):
952
- self.target_info = target_info
991
+ def __init__(self, cache: TagCache, tag_target: TagTargetTable):
953
992
  self.cache = cache
993
+ self.tag_target = tag_target
954
994
 
955
995
  @contextlib.contextmanager
956
996
  def make_session(self) -> Generator[sa_orm.Session, None, None]:
957
997
  with self.cache.make_session() as session:
958
- if not session.query(TagTargetTable).filter(TagTargetTable.sqn == self.target_info.sqn).first():
959
- raise ValueError(f"target info with sqn '{self.target_info.sqn}' is no longer present in cache")
998
+ if not session.query(TagTargetTable).filter(TagTargetTable.sqn == self.tag_target.sqn).first():
999
+ raise ValueError(f"tag target with sqn '{self.tag_target.sqn}' is no longer present in cache")
960
1000
  yield session
961
1001
 
962
- def iter_tags(
1002
+ def count_records(self) -> int:
1003
+ with self.make_session() as session:
1004
+ return (
1005
+ session
1006
+ .query(sa.func.count(TagRecordTable.sqn))
1007
+ .filter(TagRecordTable.target_sqn == self.tag_target.sqn)
1008
+ .scalar()
1009
+ )
1010
+
1011
+ def iter_records(
963
1012
  self,
964
1013
  begin_dt: datetime.datetime | None = None,
965
1014
  end_dt: datetime.datetime | None = None,
@@ -969,6 +1018,8 @@ class TargetedTagCache(object):
969
1018
  *,
970
1019
  tagsets: Sequence[Tagset] | None = None,
971
1020
  tagset_inverted: bool = False,
1021
+ skip: int | None = None,
1022
+ limit: int | None = None,
972
1023
  batch_size: int = 1000,
973
1024
  ) -> Generator[TagRecordTable, None, None]:
974
1025
  """
@@ -982,34 +1033,42 @@ class TargetedTagCache(object):
982
1033
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
983
1034
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
984
1035
  tagsets)
1036
+ :param skip: Number of records to skip (for pagination)
1037
+ :param limit: Maximum number of records to return (for pagination)
985
1038
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
986
1039
  :return: Generator of ``TagRecordTable`` instances that match the filters
987
1040
  """
988
1041
 
989
1042
  with self.make_session() as session:
990
- query = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.target_info.sqn)
1043
+ query_stmt = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.tag_target.sqn)
991
1044
  if begin_dt:
992
- query = query.filter(TagRecordTable.end_dt >= begin_dt)
1045
+ query_stmt = query_stmt.filter(TagRecordTable.end_dt >= begin_dt)
993
1046
  if end_dt:
994
- query = query.filter(TagRecordTable.begin_dt <= end_dt)
1047
+ query_stmt = query_stmt.filter(TagRecordTable.begin_dt <= end_dt)
995
1048
  if tagset_namespace:
996
- query = query.filter(TagRecordTable.tagset_namespace == tagset_namespace)
1049
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_namespace == tagset_namespace)
997
1050
  if tagset_version:
998
- query = query.filter(TagRecordTable.tagset_version == tagset_version)
1051
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_version == tagset_version)
999
1052
  if tag_prefix:
1000
- query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
1053
+ query_stmt = query_stmt.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
1001
1054
  if tagsets:
1002
1055
  if tagset_inverted:
1003
- query = query.filter(
1056
+ query_stmt = query_stmt.filter(
1004
1057
  TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
1005
1058
  else:
1006
- query = query.filter(
1059
+ query_stmt = query_stmt.filter(
1007
1060
  TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
1008
-
1009
- for result in query.yield_per(batch_size):
1061
+ if skip is not None or limit is not None:
1062
+ query_stmt = query_stmt.order_by(TagRecordTable.sqn.desc())
1063
+ if skip is not None:
1064
+ query_stmt = query_stmt.offset(skip)
1065
+ if limit is not None:
1066
+ query_stmt = query_stmt.limit(limit)
1067
+
1068
+ for result in query_stmt.yield_per(batch_size):
1010
1069
  yield result
1011
1070
 
1012
- def add_ranged_tag(
1071
+ def add_ranged_record(
1013
1072
  self,
1014
1073
  begin_dt: datetime.datetime | None,
1015
1074
  end_dt: datetime.datetime | None,
@@ -1036,9 +1095,9 @@ class TargetedTagCache(object):
1036
1095
  """
1037
1096
  with self.make_session() as session:
1038
1097
  tag_record = TagRecord(
1039
- target_sqn=self.target_info.sqn,
1040
- begin_dt=begin_dt or self.target_info.begin_dt,
1041
- end_dt=end_dt or self.target_info.end_dt,
1098
+ target_sqn=self.tag_target.sqn,
1099
+ begin_dt=begin_dt or self.tag_target.begin_dt,
1100
+ end_dt=end_dt or self.tag_target.end_dt,
1042
1101
  tagset_namespace=tag.namespace if isinstance(tag, BoundTag) else tagset_namespace,
1043
1102
  tagset_version=tag.version if isinstance(tag, BoundTag) else tagset_version,
1044
1103
  tag=tag.name if isinstance(tag, Tag) else tag,
@@ -1050,7 +1109,7 @@ class TargetedTagCache(object):
1050
1109
 
1051
1110
  return chainable(self, db_tag_record)
1052
1111
 
1053
- def add_tag(
1112
+ def add_record(
1054
1113
  self,
1055
1114
  tag: str | Tag | BoundTag,
1056
1115
  props: JsonType | None = None,
@@ -1071,16 +1130,16 @@ class TargetedTagCache(object):
1071
1130
  will be used.
1072
1131
  :return: Self instance for chaining
1073
1132
  """
1074
- return self.add_ranged_tag(
1075
- begin_dt=self.target_info.begin_dt,
1076
- end_dt=self.target_info.end_dt,
1133
+ return self.add_ranged_record(
1134
+ begin_dt=self.tag_target.begin_dt,
1135
+ end_dt=self.tag_target.end_dt,
1077
1136
  tag=tag,
1078
1137
  tagset_namespace=tagset_namespace,
1079
1138
  tagset_version=tagset_version,
1080
1139
  props=props,
1081
1140
  )
1082
1141
 
1083
- def update_tag(
1142
+ def update_record(
1084
1143
  self,
1085
1144
  sqn: int,
1086
1145
  *,
@@ -1134,7 +1193,7 @@ class TargetedTagCache(object):
1134
1193
 
1135
1194
  return chainable(self, db_tag_record)
1136
1195
 
1137
- def remove_tags(
1196
+ def remove_records(
1138
1197
  self,
1139
1198
  begin_dt: datetime.datetime | None = None,
1140
1199
  end_dt: datetime.datetime | None = None,
@@ -1159,30 +1218,37 @@ class TargetedTagCache(object):
1159
1218
  :return: Self instance for chaining
1160
1219
  """
1161
1220
  with self.make_session() as session:
1162
- query = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.target_info.sqn)
1221
+ query_stmt = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.tag_target.sqn)
1163
1222
  if begin_dt:
1164
- query = query.filter(TagRecordTable.end_dt >= begin_dt)
1223
+ query_stmt = query_stmt.filter(TagRecordTable.end_dt >= begin_dt)
1165
1224
  if end_dt:
1166
- query = query.filter(TagRecordTable.begin_dt <= end_dt)
1225
+ query_stmt = query_stmt.filter(TagRecordTable.begin_dt <= end_dt)
1167
1226
  if tagset_namespace:
1168
- query = query.filter(TagRecordTable.tagset_namespace == tagset_namespace)
1227
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_namespace == tagset_namespace)
1169
1228
  if tagset_version:
1170
- query = query.filter(TagRecordTable.tagset_version == tagset_version)
1229
+ query_stmt = query_stmt.filter(TagRecordTable.tagset_version == tagset_version)
1171
1230
  if tag_prefix:
1172
- query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
1231
+ query_stmt = query_stmt.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_prefix)}%", escape="\\"))
1173
1232
  if tagsets:
1174
1233
  if tagset_inverted:
1175
- query = query.filter(
1234
+ query_stmt = query_stmt.filter(
1176
1235
  TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
1177
1236
  else:
1178
- query = query.filter(
1237
+ query_stmt = query_stmt.filter(
1179
1238
  TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
1180
1239
 
1181
- query.delete()
1240
+ query_stmt.delete()
1182
1241
  session.commit()
1183
1242
 
1184
1243
  return self
1185
1244
 
1245
+ count_tags = count_records
1246
+ iter_tags = iter_records
1247
+ add_ranged_tag = add_ranged_record
1248
+ add_tag = add_record
1249
+ update_tag = update_record
1250
+ remove_tags = remove_records
1251
+
1186
1252
 
1187
1253
  @memorized
1188
1254
  def tag_cache(*, identifier: str | None = None, file_path: str | os.PathLike[str] | None = None) -> TagCache:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.68
3
+ Version: 1.0.70
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -16,14 +16,14 @@ plexus/common/utils/datautils.py,sha256=mgnr-dcHpw-Pk3qBud0lC3JX_pv-iKzI8llsPW9Q
16
16
  plexus/common/utils/dockerutils.py,sha256=WPxQuabRWyyM8wpSSYhb_HZaOw5yZ2TbU2dEQ2xRIlQ,5787
17
17
  plexus/common/utils/gisutils.py,sha256=UR3uVoD1nAy0SWJ1AYWCUy94Lo8zNb4nv_JdpcANBDE,11462
18
18
  plexus/common/utils/jsonutils.py,sha256=-_uKlQMLMgmVO9pB99S45Y_Vufx5dFSq43DIwGz1a54,3328
19
- plexus/common/utils/ormutils.py,sha256=CHrp6l5shRL2qa8GzRLqeVbtC-eZZkfWCOpBy32JDok,60433
19
+ plexus/common/utils/ormutils.py,sha256=bAJDSp-53406E5iySueh09yQUs0OuCshygRSzU9aaW0,60667
20
20
  plexus/common/utils/pathutils.py,sha256=hGJqSLj08tuOeZ7WeC5d4BtjnPI732BuntVQBQsqOaI,9581
21
21
  plexus/common/utils/s3utils.py,sha256=zlO4kGs-c2gUeOfPfiKIE5liQZsbYxqAZYCwA8kL0Lo,36017
22
22
  plexus/common/utils/sqlutils.py,sha256=D6kTBjhO5YlNRt3uFlPt6z3uH61m9ajEzPYmsI6NoFc,231
23
23
  plexus/common/utils/strutils.py,sha256=O9Inv4ffUTf6Xjc5ftoZwbIua1NeG7itCT9S3zjZxBc,16436
24
- plexus/common/utils/tagutils.py,sha256=n4yd4KIq8Ub4sGN8wYFom1Ea4IJ19s9UEW-rTl30NDs,52104
24
+ plexus/common/utils/tagutils.py,sha256=F1t62lxNCskirUQ9sIs5Q7hVWAF71f5Hs1aQ3Sj0X4w,55759
25
25
  plexus/common/utils/testutils.py,sha256=N8ijLu7X-hlQlHzvv0TtSsQpIF4T1hbr-AjkILoV2Ac,6152
26
- plexus_python_common-1.0.68.dist-info/METADATA,sha256=qUVFVvm5czJv0ATp6QvMPfAjPfoJGn0CCcD_ckNNfVA,1481
27
- plexus_python_common-1.0.68.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
28
- plexus_python_common-1.0.68.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
29
- plexus_python_common-1.0.68.dist-info/RECORD,,
26
+ plexus_python_common-1.0.70.dist-info/METADATA,sha256=ZZjSJxN2hxIf9b9wmR0HqftEGdvADSZuf3KNBX3ad1g,1481
27
+ plexus_python_common-1.0.70.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
28
+ plexus_python_common-1.0.70.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
29
+ plexus_python_common-1.0.70.dist-info/RECORD,,