plexus-python-common 1.0.51__tar.gz → 1.0.52__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 (92) hide show
  1. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/tagutils.py +166 -121
  3. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  4. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/tagutils_test.py +18 -17
  5. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/.editorconfig +0 -0
  6. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/.github/workflows/pr.yml +0 -0
  7. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/.github/workflows/push.yml +0 -0
  8. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/.gitignore +0 -0
  9. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/MANIFEST.in +0 -0
  10. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/README.md +0 -0
  11. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/VERSION +0 -0
  12. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/pyproject.toml +0 -0
  13. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  14. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  15. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  16. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/0-dummy +0 -0
  17. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/1-dummy +0 -0
  18. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/2-dummy +0 -0
  19. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  20. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  21. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  22. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  23. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  24. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  25. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  26. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  27. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  28. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  29. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  30. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  31. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/pathutils/dummy.txt +0 -0
  32. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  33. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  34. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  35. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  36. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  37. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  38. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  39. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  40. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  41. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  42. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  43. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  44. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/setup.cfg +0 -0
  45. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/setup.py +0 -0
  46. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/__init__.py +0 -0
  47. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/carto/OSMFile.py +0 -0
  48. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/carto/OSMNode.py +0 -0
  49. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/carto/OSMTags.py +0 -0
  50. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/carto/OSMWay.py +0 -0
  51. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/carto/__init__.py +0 -0
  52. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/pose.py +0 -0
  53. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/proj.py +0 -0
  54. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/resources/__init__.py +0 -0
  55. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/resources/tags/__init__.py +0 -0
  56. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/resources/tags/universal.tagset.yaml +0 -0
  57. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/__init__.py +0 -0
  58. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/apiutils.py +0 -0
  59. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/bagutils.py +0 -0
  60. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/config.py +0 -0
  61. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/datautils.py +0 -0
  62. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/dockerutils.py +0 -0
  63. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/jsonutils.py +0 -0
  64. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/ormutils.py +0 -0
  65. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/pathutils.py +0 -0
  66. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/s3utils.py +0 -0
  67. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/sqlutils.py +0 -0
  68. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/strutils.py +0 -0
  69. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus/common/utils/testutils.py +0 -0
  70. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
  71. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  72. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  73. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/requires.txt +0 -0
  74. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  75. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/__init__.py +0 -0
  76. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/__init__.py +0 -0
  77. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/carto/__init__.py +0 -0
  78. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  79. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  80. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/pose_test.py +0 -0
  81. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/proj_test.py +0 -0
  82. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/__init__.py +0 -0
  83. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  84. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  85. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  86. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  87. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  88. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
  89. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  90. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  91. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/plexus_tests/common/utils/testutils_test.py +0 -0
  92. {plexus_python_common-1.0.51 → plexus_python_common-1.0.52}/test/testenv.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.51
3
+ Version: 1.0.52
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -15,7 +15,6 @@ import sqlalchemy as sa
15
15
  import sqlalchemy.dialects.sqlite as sa_sqlite
16
16
  import sqlalchemy.orm as sa_orm
17
17
  from iker.common.utils.dbutils import ConnectionMaker
18
- from iker.common.utils.dtutils import dt_utc_now
19
18
  from iker.common.utils.funcutils import memorized, singleton
20
19
  from iker.common.utils.iterutils import dicttree
21
20
  from iker.common.utils.iterutils import dicttree_add, dicttree_remove
@@ -27,8 +26,8 @@ from iker.common.utils.strutils import is_blank
27
26
  from sqlmodel import Field, SQLModel
28
27
 
29
28
  from plexus.common.resources.tags import predefined_tagset_specs
30
- from plexus.common.utils.datautils import validate_bag_name, validate_dt_timezone, validate_semver
31
29
  from plexus.common.utils.datautils import validate_colon_tag, validate_snake_case, validate_vehicle_name
30
+ from plexus.common.utils.datautils import validate_dt_timezone, validate_semver, validate_slash_tag
32
31
  from plexus.common.utils.ormutils import SequenceModelMixinProtocol
33
32
  from plexus.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
34
33
  from plexus.common.utils.sqlutils import escape_sql_like
@@ -297,37 +296,38 @@ BaseModel = make_base_model()
297
296
 
298
297
 
299
298
  class TagTargetInfo(BaseModel):
299
+ name: str = Field(
300
+ sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False, unique=True),
301
+ default=None,
302
+ description="Name of the tag target",
303
+ )
300
304
  tagger_name: str = Field(
301
305
  sa_column=sa.Column(sa_sqlite.VARCHAR(128), nullable=False),
302
306
  default=None,
303
- description="Name of the tagger",
307
+ description="Name of the tagger that generates the tag records for the target",
304
308
  )
305
309
  tagger_version: str = Field(
306
310
  sa_column=sa.Column(sa_sqlite.VARCHAR(64), nullable=False),
307
311
  default=None,
308
- description="Version of the tagger",
312
+ description="Version of the tagger that generates the tag records for the target",
309
313
  )
310
- run_at: datetime.datetime = Field(
314
+ begin_dt: datetime.datetime = Field(
311
315
  sa_column=sa.Column(sa_sqlite.TIMESTAMP, nullable=False),
312
- default_factory=dt_utc_now,
313
- description="Datetime when the tagger created the tag record",
314
- )
315
- bag_name: str | None = Field(
316
- sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=True),
317
- default=None,
318
- description="Name of the target bag associated with the tag record",
319
- )
320
- begin_dt: datetime.datetime | None = Field(
321
- sa_column=sa.Column(sa_sqlite.TIMESTAMP, nullable=True),
322
316
  default=None,
323
317
  description="Begin datetime of the target range associated with the tag record",
324
318
  )
325
- end_dt: datetime.datetime | None = Field(
326
- sa_column=sa.Column(sa_sqlite.TIMESTAMP, nullable=True),
319
+ end_dt: datetime.datetime = Field(
320
+ sa_column=sa.Column(sa_sqlite.TIMESTAMP, nullable=False),
327
321
  default=None,
328
322
  description="End datetime of the target range associated with the tag record",
329
323
  )
330
324
 
325
+ @pdt.field_validator("name", mode="after")
326
+ @classmethod
327
+ def validate_name(cls, v: str) -> str:
328
+ validate_slash_tag(v)
329
+ return v
330
+
331
331
  @pdt.field_validator("tagger_name", mode="after")
332
332
  @classmethod
333
333
  def validate_tagger_name(cls, v: str) -> str:
@@ -340,45 +340,24 @@ class TagTargetInfo(BaseModel):
340
340
  validate_semver(v)
341
341
  return v
342
342
 
343
- @pdt.field_validator("run_at", mode="after")
344
- @classmethod
345
- def validate_run_at(cls, v: datetime.datetime) -> datetime.datetime:
346
- validate_dt_timezone(v)
347
- return v
348
-
349
- @pdt.field_validator("bag_name", mode="after")
350
- @classmethod
351
- def validate_bag_name(cls, v: str) -> str:
352
- if v is not None:
353
- validate_bag_name(v)
354
- return v
355
-
356
343
  @pdt.field_validator("begin_dt", mode="after")
357
344
  @classmethod
358
345
  def validate_begin_dt(cls, v: datetime.datetime) -> datetime.datetime:
359
- if v is not None:
360
- validate_dt_timezone(v)
346
+ validate_dt_timezone(v)
361
347
  return v
362
348
 
363
349
  @pdt.field_validator("end_dt", mode="after")
364
350
  @classmethod
365
351
  def validate_end_dt(cls, v: datetime.datetime) -> datetime.datetime:
366
- if v is not None:
367
- validate_dt_timezone(v)
352
+ validate_dt_timezone(v)
368
353
  return v
369
354
 
370
355
  @pdt.model_validator(mode="after")
371
356
  def validate_begin_dt_end_dt(self) -> Self:
372
- if self.begin_dt is not None and self.end_dt is not None and self.begin_dt > self.end_dt:
357
+ if self.begin_dt > self.end_dt:
373
358
  raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
374
359
  return self
375
360
 
376
- @pdt.model_validator(mode="after")
377
- def validate_bag_name_begin_dt_end_dt(self) -> Self:
378
- if self.bag_name is None and (self.begin_dt is None or self.end_dt is None):
379
- raise ValueError("bag_name is required when begin_dt or end_dt is not specified")
380
- return self
381
-
382
361
 
383
362
  class TagRecord(BaseModel):
384
363
  target_sqn: int = Field(
@@ -448,15 +427,15 @@ class TagRecordTable(TagRecord, make_sequence_model_mixin("sqlite"), table=True)
448
427
 
449
428
  if typing.TYPE_CHECKING:
450
429
  class TagTargetInfoTable(SQLModel, SequenceModelMixinProtocol):
430
+ name: sa_orm.Mapped[str] = ...
451
431
  tagger_name: sa_orm.Mapped[str] = ...
452
432
  tagger_version: sa_orm.Mapped[str] = ...
453
- run_at: sa_orm.Mapped[datetime.datetime] = ...
454
- bag_name: sa_orm.Mapped[str | None] = ...
455
- begin_dt: sa_orm.Mapped[datetime.datetime | None] = ...
456
- end_dt: sa_orm.Mapped[datetime.datetime | None] = ...
433
+ begin_dt: sa_orm.Mapped[datetime.datetime] = ...
434
+ end_dt: sa_orm.Mapped[datetime.datetime] = ...
457
435
 
458
436
 
459
437
  class TagRecordTable(SQLModel, SequenceModelMixinProtocol):
438
+ target_sqn: sa_orm.Mapped[int] = ...
460
439
  vehicle_name: sa_orm.Mapped[str] = ...
461
440
  begin_dt: sa_orm.Mapped[datetime.datetime] = ...
462
441
  end_dt: sa_orm.Mapped[datetime.datetime] = ...
@@ -494,25 +473,26 @@ class TagCache(object):
494
473
  with self.conn_maker.make_session() as session:
495
474
  yield session
496
475
 
476
+ def get_target(self, name: str) -> TagTargetInfoTable | None:
477
+ with self.make_session() as session:
478
+ return session.query(TagTargetInfoTable).filter(TagTargetInfoTable.name == name).one_or_none()
479
+
497
480
  def query_targets(
498
481
  self,
482
+ name: str | None = None,
499
483
  tagger_name: str | None = None,
500
484
  tagger_version: str | None = None,
501
- run_at: datetime.datetime | None = None,
502
- bag_name: str | None = None,
503
485
  begin_dt: datetime.datetime | None = None,
504
486
  end_dt: datetime.datetime | None = None,
505
487
  ) -> Generator[TagTargetInfoTable, None, None]:
506
488
  with self.make_session() as session:
507
489
  query = session.query(TagTargetInfoTable)
490
+ if name:
491
+ query = query.filter(TagTargetInfoTable.name == name)
508
492
  if tagger_name:
509
493
  query = query.filter(TagTargetInfoTable.tagger_name == tagger_name)
510
494
  if tagger_version:
511
495
  query = query.filter(TagTargetInfoTable.tagger_version == tagger_version)
512
- if run_at:
513
- query = query.filter(TagTargetInfoTable.run_at >= run_at)
514
- if bag_name:
515
- query = query.filter(TagTargetInfoTable.bag_name == bag_name)
516
496
  if begin_dt:
517
497
  query = query.filter(TagTargetInfoTable.end_dt >= begin_dt)
518
498
  if end_dt:
@@ -522,48 +502,42 @@ class TagCache(object):
522
502
 
523
503
  def add_target(
524
504
  self,
505
+ name: str,
525
506
  tagger_name: str,
526
507
  tagger_version: str,
527
- run_at: datetime.datetime | None = None,
528
- bag_name: str | None = None,
529
- begin_dt: datetime.datetime | None = None,
530
- end_dt: datetime.datetime | None = None,
508
+ begin_dt: datetime.datetime,
509
+ end_dt: datetime.datetime,
531
510
  ) -> TagTargetInfoTable:
532
511
  with self.make_session() as session:
533
512
  target_info = TagTargetInfo(
513
+ name=name,
534
514
  tagger_name=tagger_name,
535
515
  tagger_version=tagger_version,
536
- run_at=run_at or dt_utc_now(),
537
- bag_name=bag_name,
538
516
  begin_dt=begin_dt,
539
517
  end_dt=end_dt,
540
518
  )
541
519
  db_target_info = clone_sequence_model_instance(TagTargetInfoTable, target_info)
542
520
  session.add(db_target_info)
543
521
  session.commit()
544
- session.refresh(db_target_info)
545
522
 
546
- return db_target_info
523
+ return self.get_target(name)
547
524
 
548
525
  def remove_targets(
549
526
  self,
527
+ name: str | None = None,
550
528
  tagger_name: str | None = None,
551
529
  tagger_version: str | None = None,
552
- run_at: datetime.datetime | None = None,
553
- bag_name: str | None = None,
554
530
  begin_dt: datetime.datetime | None = None,
555
531
  end_dt: datetime.datetime | None = None,
556
532
  ):
557
533
  with self.make_session() as session:
558
534
  query = session.query(TagTargetInfoTable)
535
+ if name:
536
+ query = query.filter(TagTargetInfoTable.name == name)
559
537
  if tagger_name:
560
538
  query = query.filter(TagTargetInfoTable.tagger_name == tagger_name)
561
539
  if tagger_version:
562
540
  query = query.filter(TagTargetInfoTable.tagger_version == tagger_version)
563
- if run_at:
564
- query = query.filter(TagTargetInfoTable.run_at >= run_at)
565
- if bag_name:
566
- query = query.filter(TagTargetInfoTable.bag_name == bag_name)
567
541
  if begin_dt:
568
542
  query = query.filter(TagTargetInfoTable.end_dt >= begin_dt)
569
543
  if end_dt:
@@ -580,29 +554,17 @@ class TagCache(object):
580
554
  )
581
555
  session.commit()
582
556
 
583
- def with_target(
584
- self,
585
- tagger_name: str | None = None,
586
- tagger_version: str | None = None,
587
- run_at: datetime.datetime | None = None,
588
- bag_name: str | None = None,
589
- begin_dt: datetime.datetime | None = None,
590
- end_dt: datetime.datetime | None = None,
591
- *,
592
- target_info: TagTargetInfoTable | None = None,
593
- ) -> "TargetedTagCache":
594
- if target_info is not None:
595
- return TargetedTagCache(cache=self, target_info=target_info)
596
- return TargetedTagCache(
597
- cache=self,
598
- target_info=self.add_target(tagger_name, tagger_version, run_at, bag_name, begin_dt, end_dt),
599
- )
557
+ def with_target(self, name: str) -> "TargetedTagCache":
558
+ target_info = self.get_target(name)
559
+ if target_info is None:
560
+ raise ValueError(f"target with name '{name}' not found in cache")
561
+ return TargetedTagCache(cache=self, target_info=target_info)
600
562
 
601
563
  def query(
602
564
  self,
603
565
  vehicle_name: str | None = None,
604
- begin_time: datetime.datetime | None = None,
605
- end_time: datetime.datetime | None = None,
566
+ begin_dt: datetime.datetime | None = None,
567
+ end_dt: datetime.datetime | None = None,
606
568
  tag_pattern: str | None = None,
607
569
  *,
608
570
  tagsets: Sequence[Tagset] | None = None,
@@ -612,8 +574,8 @@ class TagCache(object):
612
574
  Query tag records in the cache with optional filters.
613
575
 
614
576
  :param vehicle_name: Filter by vehicle name (exact match)
615
- :param begin_time: Filter by begin time (inclusive)
616
- :param end_time: Filter by end time (inclusive)
577
+ :param begin_dt: Filter by begin time (inclusive)
578
+ :param end_dt: Filter by end time (inclusive)
617
579
  :param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
618
580
  with "dummy_tag:")
619
581
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
@@ -625,12 +587,79 @@ class TagCache(object):
625
587
  query = session.query(TagRecordTable)
626
588
  if vehicle_name:
627
589
  query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
628
- if begin_time:
629
- query = query.filter(TagRecordTable.end_dt >= begin_time)
630
- if end_time:
631
- query = query.filter(TagRecordTable.begin_dt <= end_time)
590
+ if begin_dt:
591
+ query = query.filter(TagRecordTable.end_dt >= begin_dt)
592
+ if end_dt:
593
+ query = query.filter(TagRecordTable.begin_dt <= end_dt)
594
+ if tag_pattern:
595
+ query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
596
+ if tagsets:
597
+ if tagset_inverted:
598
+ query = query.filter(
599
+ TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
600
+ else:
601
+ query = query.filter(
602
+ TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
603
+
604
+ yield from query.all()
605
+
606
+ def query_with_targets(
607
+ self,
608
+ vehicle_name: str | None = None,
609
+ begin_dt: datetime.datetime | None = None,
610
+ end_dt: datetime.datetime | None = None,
611
+ tag_pattern: str | None = None,
612
+ *,
613
+ target_name: str | None = None,
614
+ target_tagger_name: str | None = None,
615
+ target_tagger_version: str | None = None,
616
+ target_begin_dt: datetime.datetime | None = None,
617
+ target_end_dt: datetime.datetime | None = None,
618
+ tagsets: Sequence[Tagset] | None = None,
619
+ tagset_inverted: bool = False,
620
+ ) -> Generator[tuple[TagRecordTable, TagTargetInfoTable], None, None]:
621
+ """
622
+ Query tag records in the cache with their associated target info, with optional filters.
623
+
624
+ :param vehicle_name: Filter by vehicle name (exact match)
625
+ :param begin_dt: Filter by begin time (inclusive)
626
+ :param end_dt: Filter by end time (inclusive)
627
+ :param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
628
+ with "dummy_tag:")
629
+ :param target_name: Filter by target name (exact match)
630
+ :param target_tagger_name: Filter by target tagger name (exact match)
631
+ :param target_tagger_version: Filter by target tagger version (exact match)
632
+ :param target_begin_dt: Filter by target begin time (inclusive)
633
+ :param target_end_dt: Filter by target end time (inclusive)
634
+ :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
635
+ :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
636
+ tagsets)
637
+ :return: Generator of ``TagRecordTable`` instances that match the filters
638
+ """
639
+ with self.make_session() as session:
640
+ query = (
641
+ session
642
+ .query(TagRecordTable, TagTargetInfoTable)
643
+ .join(TagTargetInfoTable, TagRecordTable.target_sqn == TagTargetInfoTable.sqn)
644
+ )
645
+ if vehicle_name:
646
+ query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
647
+ if begin_dt:
648
+ query = query.filter(TagRecordTable.end_dt >= begin_dt)
649
+ if end_dt:
650
+ query = query.filter(TagRecordTable.begin_dt <= end_dt)
632
651
  if tag_pattern:
633
652
  query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
653
+ if target_name:
654
+ query = query.filter(TagTargetInfoTable.name == target_name)
655
+ if target_tagger_name:
656
+ query = query.filter(TagTargetInfoTable.tagger_name == target_tagger_name)
657
+ if target_tagger_version:
658
+ query = query.filter(TagTargetInfoTable.tagger_version == target_tagger_version)
659
+ if target_begin_dt:
660
+ query = query.filter(TagTargetInfoTable.end_dt >= target_begin_dt)
661
+ if target_end_dt:
662
+ query = query.filter(TagTargetInfoTable.begin_dt <= target_end_dt)
634
663
  if tagsets:
635
664
  if tagset_inverted:
636
665
  query = query.filter(
@@ -644,8 +673,8 @@ class TagCache(object):
644
673
  def remove(
645
674
  self,
646
675
  vehicle_name: str | None = None,
647
- begin_time: datetime.datetime | None = None,
648
- end_time: datetime.datetime | None = None,
676
+ begin_dt: datetime.datetime | None = None,
677
+ end_dt: datetime.datetime | None = None,
649
678
  tag_pattern: str | None = None,
650
679
  *,
651
680
  tagsets: Sequence[Tagset] | None = None,
@@ -655,23 +684,22 @@ class TagCache(object):
655
684
  Remove tag records from the cache that match the specified filters.
656
685
 
657
686
  :param vehicle_name: Filter by vehicle name (exact match)
658
- :param begin_time: Filter by begin time (inclusive)
659
- :param end_time: Filter by end time (inclusive)
687
+ :param begin_dt: Filter by begin time (inclusive)
688
+ :param end_dt: Filter by end time (inclusive)
660
689
  :param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
661
690
  with "dummy_tag:")
662
691
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
663
692
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
664
693
  tagsets)
665
- :return: Self instance for chaining
666
694
  """
667
695
  with self.make_session() as session:
668
696
  query = session.query(TagRecordTable)
669
697
  if vehicle_name:
670
698
  query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
671
- if begin_time:
672
- query = query.filter(TagRecordTable.end_dt >= begin_time)
673
- if end_time:
674
- query = query.filter(TagRecordTable.begin_dt <= end_time)
699
+ if begin_dt:
700
+ query = query.filter(TagRecordTable.end_dt >= begin_dt)
701
+ if end_dt:
702
+ query = query.filter(TagRecordTable.begin_dt <= end_dt)
675
703
  if tag_pattern:
676
704
  query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
677
705
  if tagsets:
@@ -736,8 +764,8 @@ class TargetedTagCache(object):
736
764
  def query(
737
765
  self,
738
766
  vehicle_name: str | None = None,
739
- begin_time: datetime.datetime | None = None,
740
- end_time: datetime.datetime | None = None,
767
+ begin_dt: datetime.datetime | None = None,
768
+ end_dt: datetime.datetime | None = None,
741
769
  tag_pattern: str | None = None,
742
770
  *,
743
771
  tagsets: Sequence[Tagset] | None = None,
@@ -747,8 +775,8 @@ class TargetedTagCache(object):
747
775
  Query tag records in the cache with optional filters.
748
776
 
749
777
  :param vehicle_name: Filter by vehicle name (exact match)
750
- :param begin_time: Filter by begin time (inclusive)
751
- :param end_time: Filter by end time (inclusive)
778
+ :param begin_dt: Filter by begin time (inclusive)
779
+ :param end_dt: Filter by end time (inclusive)
752
780
  :param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
753
781
  with "dummy_tag:")
754
782
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
@@ -761,10 +789,10 @@ class TargetedTagCache(object):
761
789
  query = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.target_info.sqn)
762
790
  if vehicle_name:
763
791
  query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
764
- if begin_time:
765
- query = query.filter(TagRecordTable.end_dt >= begin_time)
766
- if end_time:
767
- query = query.filter(TagRecordTable.begin_dt <= end_time)
792
+ if begin_dt:
793
+ query = query.filter(TagRecordTable.end_dt >= begin_dt)
794
+ if end_dt:
795
+ query = query.filter(TagRecordTable.begin_dt <= end_dt)
768
796
  if tag_pattern:
769
797
  query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
770
798
  if tagsets:
@@ -780,8 +808,8 @@ class TargetedTagCache(object):
780
808
  def add(
781
809
  self,
782
810
  vehicle_name: str,
783
- begin_time: datetime.datetime | None,
784
- end_time: datetime.datetime | None,
811
+ begin_dt: datetime.datetime | None,
812
+ end_dt: datetime.datetime | None,
785
813
  tag: str | Tag,
786
814
  props: JsonType | None = None,
787
815
  ) -> Self:
@@ -789,8 +817,8 @@ class TargetedTagCache(object):
789
817
  Add a tag record to the cache.
790
818
 
791
819
  :param vehicle_name: Vehicle name associated with the tag record
792
- :param begin_time: Begin datetime of the tag record
793
- :param end_time: End datetime of the tag record
820
+ :param begin_dt: Begin datetime of the tag record
821
+ :param end_dt: End datetime of the tag record
794
822
  :param tag: Tag name or Tag instance to be added (if Tag instance is provided, its name will be used)
795
823
  :param props: Additional properties of the tag record in JSON format (optional)
796
824
  :return: Self instance for chaining
@@ -799,8 +827,8 @@ class TargetedTagCache(object):
799
827
  tag_record = TagRecord(
800
828
  target_sqn=self.target_info.sqn,
801
829
  vehicle_name=vehicle_name,
802
- begin_dt=begin_time or self.target_info.begin_dt,
803
- end_dt=end_time or self.target_info.end_dt,
830
+ begin_dt=begin_dt or self.target_info.begin_dt,
831
+ end_dt=end_dt or self.target_info.end_dt,
804
832
  tag=tag.name if isinstance(tag, Tag) else tag,
805
833
  props=props,
806
834
  )
@@ -809,11 +837,28 @@ class TargetedTagCache(object):
809
837
 
810
838
  return self
811
839
 
840
+ def add_for_target(self, vehicle_name: str, tag: str | Tag, props: JsonType | None = None) -> Self:
841
+ """
842
+ Add a tag record to the cache for the entire target range.
843
+
844
+ :param vehicle_name: Vehicle name associated with the tag record
845
+ :param tag: Tag name or Tag instance to be added (if Tag instance is provided, its name will be used)
846
+ :param props: Additional properties of the tag record in JSON format (optional)
847
+ :return: Self instance for chaining
848
+ """
849
+ return self.add(
850
+ vehicle_name=vehicle_name,
851
+ begin_dt=self.target_info.begin_dt,
852
+ end_dt=self.target_info.end_dt,
853
+ tag=tag,
854
+ props=props,
855
+ )
856
+
812
857
  def remove(
813
858
  self,
814
859
  vehicle_name: str | None = None,
815
- begin_time: datetime.datetime | None = None,
816
- end_time: datetime.datetime | None = None,
860
+ begin_dt: datetime.datetime | None = None,
861
+ end_dt: datetime.datetime | None = None,
817
862
  tag_pattern: str | None = None,
818
863
  *,
819
864
  tagsets: Sequence[Tagset] | None = None,
@@ -823,8 +868,8 @@ class TargetedTagCache(object):
823
868
  Remove tag records from the cache that match the specified filters.
824
869
 
825
870
  :param vehicle_name: Filter by vehicle name (exact match)
826
- :param begin_time: Filter by begin time (inclusive)
827
- :param end_time: Filter by end time (inclusive)
871
+ :param begin_dt: Filter by begin time (inclusive)
872
+ :param end_dt: Filter by end time (inclusive)
828
873
  :param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
829
874
  with "dummy_tag:")
830
875
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
@@ -836,10 +881,10 @@ class TargetedTagCache(object):
836
881
  query = session.query(TagRecordTable).filter(TagRecordTable.target_sqn == self.target_info.sqn)
837
882
  if vehicle_name:
838
883
  query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
839
- if begin_time:
840
- query = query.filter(TagRecordTable.end_dt >= begin_time)
841
- if end_time:
842
- query = query.filter(TagRecordTable.begin_dt <= end_time)
884
+ if begin_dt:
885
+ query = query.filter(TagRecordTable.end_dt >= begin_dt)
886
+ if end_dt:
887
+ query = query.filter(TagRecordTable.begin_dt <= end_dt)
843
888
  if tag_pattern:
844
889
  query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
845
890
  if tagsets:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.51
3
+ Version: 1.0.52
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -49,12 +49,13 @@ class TagUtilsTest(unittest.TestCase):
49
49
  self.assertEqual(cache.file_path, tag_cache_file_path())
50
50
  self.assertTrue(cache.file_path.exists())
51
51
 
52
- target_cache = cache.with_target("awesome_tagger",
53
- "1.0.0",
54
- dt_parse_iso("2020-01-01T00:00:00+00:00"),
55
- "20200101T000000-dummy_vehicle-0.bag",
56
- dt_parse_iso("2020-01-01T00:00:00+00:00"),
57
- dt_parse_iso("2020-01-01T01:00:00+00:00"))
52
+ cache.add_target("awesome_tagger/20200101_000000/dummy_vehicle/0",
53
+ "awesome_tagger",
54
+ "1.0.0",
55
+ dt_parse_iso("2020-01-01T00:00:00+00:00"),
56
+ dt_parse_iso("2020-01-01T01:00:00+00:00"))
57
+
58
+ target_cache = cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
58
59
 
59
60
  tags_count = 1000
60
61
 
@@ -165,15 +166,15 @@ class TagUtilsTest(unittest.TestCase):
165
166
 
166
167
  target_caches_count = 10
167
168
 
168
- target_caches = [
169
- cache.with_target(f"concurrent_tagger_{i}",
170
- "2.0.0",
171
- dt_parse_iso("2020-01-01T00:00:00+00:00"),
172
- f"20200101T000000-dummy_vehicle-{i}.bag",
173
- dt_parse_iso("2020-01-01T00:00:00+00:00"),
174
- dt_parse_iso("2020-01-01T01:00:00+00:00"))
175
- for i in range(target_caches_count)
176
- ]
169
+ for i in range(target_caches_count):
170
+ cache.add_target(f"concurrent_tagger/20200101_000000/dummy_vehicle/{i}",
171
+ "concurrent_tagger",
172
+ f"1.0.{i}",
173
+ dt_parse_iso("2020-01-01T00:00:00+00:00"),
174
+ dt_parse_iso("2020-01-01T01:00:00+00:00"))
175
+
176
+ target_caches = [cache.with_target(f"concurrent_tagger/20200101_000000/dummy_vehicle/{i}")
177
+ for i in range(target_caches_count)]
177
178
 
178
179
  threads_count_per_target_cache = 10
179
180
  tasks_count_per_thread = 100
@@ -204,6 +205,6 @@ class TagUtilsTest(unittest.TestCase):
204
205
  self.assertEqual(len(list(target_cache.query("dummy_vehicle", tag_pattern="dummy:bar"))),
205
206
  tasks_count_per_target_cache // len(tags))
206
207
 
207
- self.assertEqual(len(list(cache.query("dummy_vehicle"))), total_tasks_count)
208
- self.assertEqual(len(list(cache.query("dummy_vehicle", tag_pattern="dummy:bar"))),
208
+ self.assertEqual(len(list(cache.query_with_targets("dummy_vehicle"))), total_tasks_count)
209
+ self.assertEqual(len(list(cache.query_with_targets("dummy_vehicle", tag_pattern="dummy:bar"))),
209
210
  total_tasks_count // len(tags))