plexus-python-common 1.0.57__tar.gz → 1.0.59__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 (94) hide show
  1. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/bagutils.py +14 -12
  3. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/ormutils.py +9 -9
  4. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/tagutils.py +26 -20
  5. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  6. plexus_python_common-1.0.59/test/plexus_tests/common/utils/jsonutils_test.py +37 -0
  7. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/tagutils_test.py +164 -92
  8. plexus_python_common-1.0.57/test/plexus_tests/common/utils/jsonutils_test.py +0 -15
  9. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/.editorconfig +0 -0
  10. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/.github/workflows/pr.yml +0 -0
  11. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/.github/workflows/push.yml +0 -0
  12. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/.gitignore +0 -0
  13. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/MANIFEST.in +0 -0
  14. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/README.md +0 -0
  15. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/VERSION +0 -0
  16. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/pyproject.toml +0 -0
  17. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  18. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  19. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  20. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/0-dummy +0 -0
  21. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/1-dummy +0 -0
  22. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/2-dummy +0 -0
  23. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  24. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  25. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  26. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  27. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  28. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  29. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  30. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  31. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  32. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  33. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  34. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  35. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/pathutils/dummy.txt +0 -0
  36. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  37. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  38. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  39. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  40. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  41. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  42. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  43. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  44. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  45. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  46. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  47. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  48. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/setup.cfg +0 -0
  49. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/setup.py +0 -0
  50. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/__init__.py +0 -0
  51. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/carto/OSMFile.py +0 -0
  52. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/carto/OSMNode.py +0 -0
  53. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/carto/OSMTags.py +0 -0
  54. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/carto/OSMWay.py +0 -0
  55. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/carto/__init__.py +0 -0
  56. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/pose.py +0 -0
  57. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/proj.py +0 -0
  58. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/resources/__init__.py +0 -0
  59. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/resources/tags/__init__.py +0 -0
  60. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
  61. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
  62. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/__init__.py +0 -0
  63. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/apiutils.py +0 -0
  64. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/config.py +0 -0
  65. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/datautils.py +0 -0
  66. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/dockerutils.py +0 -0
  67. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/jsonutils.py +0 -0
  68. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/pathutils.py +0 -0
  69. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/s3utils.py +0 -0
  70. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/sqlutils.py +0 -0
  71. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/strutils.py +0 -0
  72. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus/common/utils/testutils.py +0 -0
  73. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
  74. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  75. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  76. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/requires.txt +0 -0
  77. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  78. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/__init__.py +0 -0
  79. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/__init__.py +0 -0
  80. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/carto/__init__.py +0 -0
  81. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  82. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  83. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/pose_test.py +0 -0
  84. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/proj_test.py +0 -0
  85. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/__init__.py +0 -0
  86. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  87. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  88. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  89. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  90. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
  91. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  92. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  93. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/test/plexus_tests/common/utils/testutils_test.py +0 -0
  94. {plexus_python_common-1.0.57 → plexus_python_common-1.0.59}/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.57
3
+ Version: 1.0.59
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -148,18 +148,18 @@ class BagReader(AbstractContextManager, BagIOBase):
148
148
  @staticmethod
149
149
  def open_ros2(db_dir: str | os.PathLike[str], db_filename: str = default_bag_db_file, **kwargs) -> "BagReader":
150
150
  """
151
- Creates a BagReader instance to read messages from a ROS2 bag file in the specified directory.
151
+ Creates a ``BagReader`` instance to read messages from a ROS2 bag file in the specified directory.
152
152
 
153
153
  :param db_dir: path to the directory where the SQLite DB file is located.
154
154
  :param db_filename: name of the SQLite DB file to read. Default is "bag.db".
155
- :param kwargs: additional keyword arguments to pass to the BagReader constructor.
156
- :return: a BagReader instance.
155
+ :param kwargs: additional keyword arguments to pass to the ``BagReader`` constructor.
156
+ :return: a ``BagReader`` instance.
157
157
  """
158
158
  return BagReader(make_path(db_dir) / db_filename)
159
159
 
160
160
  def __init__(self, bag_file_path: str | os.PathLike[str]):
161
161
  """
162
- Creates a BagReader instance to read messages from a ROS2 bag file.
162
+ Creates a ``BagReader`` instance to read messages from a ROS2 bag file.
163
163
 
164
164
  :param bag_file_path: path to the SQLite DB file to read.
165
165
  """
@@ -202,24 +202,26 @@ class BagWriter(AbstractContextManager, BagIOBase):
202
202
  @staticmethod
203
203
  def open_ros2(db_dir: str | os.PathLike[str], db_filename: str = default_bag_db_file, **kwargs) -> "BagWriter":
204
204
  """
205
- Creates a BagWriter instance to write messages to a ROS2 bag file in the specified directory.
205
+ Creates a ``BagWriter`` instance to write messages to a ROS2 bag file in the specified directory.
206
206
 
207
207
  :param db_dir: path to the directory where the SQLite DB file will be created.
208
208
  :param db_filename: name of the SQLite DB file to create. Default is "bag.db".
209
- :param kwargs: additional keyword arguments to pass to the BagWriter constructor.
210
- :return: a BagWriter instance.
209
+ :param kwargs: additional keyword arguments to pass to the ``BagWriter`` constructor.
210
+ :return: a ``BagWriter`` instance.
211
211
  """
212
212
  return BagWriter(make_path(db_dir) / db_filename, **kwargs)
213
213
 
214
214
  def __init__(self, bag_file_path: str | os.PathLike[str], *, overwrite: bool = True, exist_ok: bool = False):
215
215
  """
216
- Creates a BagWriter instance to write messages to a ROS2 bag file.
216
+ Creates a ``BagWriter`` instance to write messages to a ROS2 bag file.
217
217
 
218
218
  :param bag_file_path: path to the SQLite DB file to create.
219
- :param overwrite: whether to overwrite the SQLite DB file if it already exists. If False and the file already
220
- exists, a FileExistsError is raised. If True, the existing file is deleted and a new one is created.
221
- :param exist_ok: whether to ignore if the SQLite DB file already exists. If False and the file already exists,
222
- a FileExistsError is raised. If True, the existing file is used and no new file is created.
219
+ :param overwrite: whether to overwrite the SQLite DB file if it already exists. If ``False`` and the file
220
+ already exists, a ``FileExistsError`` is raised. If ``True``, the existing file is deleted
221
+ and a new one is created.
222
+ :param exist_ok: whether to ignore if the SQLite DB file already exists. If ``False`` and the file already
223
+ exists, a ``FileExistsError`` is raised. If ``True``, the existing file is used and no new
224
+ file is created.
223
225
  """
224
226
  super().__init__(bag_file_path)
225
227
 
@@ -451,11 +451,11 @@ def model_revision_type(dialect: str | None = None) -> sa.types.TypeEngine[int]:
451
451
 
452
452
  def make_sequence_model_mixin(dialect: str | None = None) -> type[SequenceModelMixin]:
453
453
  """
454
- Creates a mixin class for SQLModel models that adds a unique identifier field `sqn`.
454
+ Creates a mixin class for SQLModel models that adds a unique identifier field ``sqn``.
455
455
  Use this mixin to add an auto-incremented primary key to your models.
456
456
 
457
- :param dialect: The database dialect to determine the column type for the `sqn` field.
458
- :return: A mixin class that can be used with SQLModel models to add the `sqn` field.
457
+ :param dialect: The database dialect to determine the column type for the ``sqn`` field.
458
+ :return: A mixin class that can be used with SQLModel models to add the ``sqn`` field.
459
459
  """
460
460
 
461
461
  class ModelMixin(SQLModel):
@@ -765,11 +765,11 @@ class RevisionModel(make_base_model(), make_revision_model_mixin(), table=True):
765
765
  def make_snapshot_model_trigger[SnapshotModelT: SnapshotModelMixin](engine: sa.Engine, model: type[SnapshotModelT]):
766
766
  """
767
767
  Creates the necessary database objects (sequence, function, trigger) to support automatic snapshot management
768
- for the given snapshot model. This includes a sequence for `record_sqn`, a function to handle snapshot updates,
769
- and a trigger to invoke the function before inserts. The model must extend `SnapshotModel`.
768
+ for the given snapshot model. This includes a sequence for ``record_sqn``, a function to handle snapshot updates,
769
+ and a trigger to invoke the function before inserts. The model must extend ``SnapshotModel``.
770
770
 
771
771
  :param engine: SQLAlchemy engine connected to the target database.
772
- :param model: The snapshot model class extending `SnapshotModel`.
772
+ :param model: The snapshot model class extending ``SnapshotModel``.
773
773
  """
774
774
  table_name = model_name_of(model, fallback_classname=False)
775
775
  if not table_name:
@@ -834,11 +834,11 @@ def make_snapshot_model_trigger[SnapshotModelT: SnapshotModelMixin](engine: sa.E
834
834
  def make_revision_model_trigger[RevisionModelT: RevisionModelMixin](engine: sa.Engine, model: type[RevisionModelT]):
835
835
  """
836
836
  Creates the necessary database objects (sequence, function, trigger) to support automatic revision management
837
- for the given revision model. This includes a sequence for `record_sqn`, a function to handle revision updates,
838
- and a trigger to invoke the function before inserts. The model must extend `RevisionModel`.
837
+ for the given revision model. This includes a sequence for ``record_sqn``, a function to handle revision updates,
838
+ and a trigger to invoke the function before inserts. The model must extend ``RevisionModel``.
839
839
 
840
840
  :param engine: SQLAlchemy engine connected to the target database.
841
- :param model: The revision model class extending `RevisionModel`.
841
+ :param model: The revision model class extending ``RevisionModel``.
842
842
  """
843
843
  table_name = model_name_of(model, fallback_classname=False)
844
844
  if not table_name:
@@ -69,7 +69,8 @@ class Tag(object):
69
69
 
70
70
  @property
71
71
  def parent_tag_name(self) -> str | None:
72
- return head_or_none(self.name.rsplit(":", 1))
72
+ name = head_or_none(self.name.rsplit(":", 1))
73
+ return None if is_blank(name) or name == self.name else name
73
74
 
74
75
  def unbind(self) -> Self:
75
76
  return self
@@ -155,18 +156,18 @@ class Tagset(Sequence[Tag], Mapping[str, Tag]):
155
156
  def tag_names(self) -> list[str]:
156
157
  return list(self.tags_dict.keys())
157
158
 
158
- def child_tags(self, parent: str | Tag | BoundTag) -> list[BoundTag]:
159
+ def child_tags(self, parent: str | Tag | BoundTag) -> list[Tag]:
159
160
  parent = self.get(parent)
160
161
  if parent is None:
161
162
  return []
162
163
  subtree = dicttree_subtree(self.tags_tree, parent.tag_parts)
163
- return list(self.bind(tag) for tag in dicttree_children(subtree)) if subtree else []
164
+ return list(dicttree_children(subtree)) if subtree else []
164
165
 
165
- def parent_tags(self, child: str | Tag | BoundTag) -> list[BoundTag]:
166
+ def parent_tags(self, child: str | Tag | BoundTag) -> list[Tag]:
166
167
  child = self.get(child)
167
168
  if child is None:
168
169
  return []
169
- return list(self.bind(tag) for tag in dicttree_lineage(self.tags_tree, child.tag_parts[:-1]))
170
+ return list(dicttree_lineage(self.tags_tree, child.tag_parts[:-1]))
170
171
 
171
172
  def validate(self, tag_name: str, props: JsonType | None, *, raise_on_error: bool = False) -> bool:
172
173
  tag = self.get(tag_name)
@@ -687,7 +688,7 @@ class TagCache(object):
687
688
  :param tag_prefix: Filter by tag name prefix, e.g. "dummy_tag:" to match all tags starting with "dummy_tag:"
688
689
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
689
690
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
690
- tagsets)
691
+ tagsets)
691
692
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
692
693
  :return: Generator of ``TagRecordTable`` instances that match the filters
693
694
  """
@@ -748,7 +749,7 @@ class TagCache(object):
748
749
  :param target_end_dt: Filter by target end time (inclusive)
749
750
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
750
751
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
751
- tagsets)
752
+ tagsets)
752
753
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
753
754
  :return: Generator of ``TagRecordTable`` instances that match the filters
754
755
  """
@@ -812,7 +813,7 @@ class TagCache(object):
812
813
  :param tag_prefix: Filter by tag name prefix, e.g. "dummy_tag:" to match all tags starting with "dummy_tag:"
813
814
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
814
815
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
815
- tagsets)
816
+ tagsets)
816
817
  """
817
818
  with self.make_session() as session:
818
819
  query = session.query(TagRecordTable)
@@ -920,7 +921,7 @@ class TargetedTagCache(object):
920
921
  :param tag_prefix: Filter by tag name prefix, e.g. "dummy_tag:" to match all tags starting with "dummy_tag:"
921
922
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
922
923
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
923
- tagsets)
924
+ tagsets)
924
925
  :param batch_size: Number of records to fetch per batch from the database (for memory efficiency)
925
926
  :return: Generator of ``TagRecordTable`` instances that match the filters
926
927
  """
@@ -964,12 +965,13 @@ class TargetedTagCache(object):
964
965
  :param begin_dt: Begin datetime of the tag record
965
966
  :param end_dt: End datetime of the tag record
966
967
  :param tag: Tag name or ``Tag``/``BoundTag`` instance to be added. If ``Tag``/``BoundTag`` instance is provided,
967
- its name will be used.
968
+ its name will be used.
968
969
  :param props: Additional properties of the tag record in JSON format (optional)
969
970
  :param tagset_namespace: Namespace of the tagset that the tag belongs to. If the ``tag`` parameter is a
970
- ``BoundTag`` instance, this parameter will be ignored and the namespace from the instance will be used.
971
- :param tagset_version: Version of the tagset that the tag belongs to. If the ``tag`` parameter is a
972
- ``BoundTag`` instance, this parameter will be ignored and the version from the instance will be used.
971
+ ``BoundTag`` instance, this parameter will be ignored and the namespace from the
972
+ instance will be used.
973
+ :param tagset_version: Version of the tagset that the tag belongs to. If the ``tag`` parameter is a ``BoundTag``
974
+ instance, this parameter will be ignored and the version from the instance will be used.
973
975
  :return: Self instance for chaining
974
976
  """
975
977
  with self.make_session() as session:
@@ -998,12 +1000,14 @@ class TargetedTagCache(object):
998
1000
  Add a tag record to the cache for the entire target range.
999
1001
 
1000
1002
  :param tag: Tag name or ``Tag``/``BoundTag`` instance to be added. If ``Tag``/``BoundTag`` instance is provided,
1001
- its name will be used.
1003
+ its name will be used.
1002
1004
  :param props: Additional properties of the tag record in JSON format (optional)
1003
1005
  :param tagset_namespace: Namespace of the tagset that the tag belongs to. If the ``tag`` parameter is a
1004
- ``BoundTag`` instance, this parameter will be ignored and the namespace from the instance will be used.
1006
+ ``BoundTag`` instance, this parameter will be ignored and the namespace from the
1007
+ instance will be used.
1005
1008
  :param tagset_version: Version of the tagset that the tag belongs to. If the ``tag`` parameter is a
1006
- ``BoundTag`` instance, this parameter will be ignored and the version from the instance will be used.
1009
+ ``BoundTag`` instance, this parameter will be ignored and the version from the instance
1010
+ will be used.
1007
1011
  :return: Self instance for chaining
1008
1012
  """
1009
1013
  return self.add_ranged_tag(
@@ -1036,7 +1040,7 @@ class TargetedTagCache(object):
1036
1040
  :param tag_prefix: Filter by tag name prefix, e.g. "dummy_tag:" to match all tags starting with "dummy_tag:"
1037
1041
  :param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
1038
1042
  :param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
1039
- tagsets)
1043
+ tagsets)
1040
1044
  :return: Self instance for chaining
1041
1045
  """
1042
1046
  with self.make_session() as session:
@@ -1073,10 +1077,12 @@ def tag_cache(*, identifier: str | None = None, file_path: str | None = None) ->
1073
1077
  string and return a ``TagCache`` instance associated with a file path derived from the identifier.
1074
1078
 
1075
1079
  :param identifier: An optional string identifier for the tag cache. If provided, it must be in snake case format
1076
- and will be used to derive the file path for the tag cache. If not provided, a default file path will be used.
1080
+ and will be used to derive the file path for the tag cache. If not provided, a default file path
1081
+ will be used.
1077
1082
  :param file_path: An optional file path for the tag cache. If provided, it will be used directly. If not provided,
1078
- the file path will be derived from the identifier if it is provided, or a default file path will be used if the
1079
- identifier is not provided. Note that both 'identifier' and 'file_path' cannot be specified at the same time.
1083
+ the file path will be derived from the identifier if it is provided, or a default file path will
1084
+ be used if the identifier is not provided. Note that both ``identifier`` and ``file_path`` cannot
1085
+ be specified at the same time.
1080
1086
  :return: A ``TagCache`` instance associated with the specified or default file path.
1081
1087
  """
1082
1088
  if identifier is not None and file_path is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.57
3
+ Version: 1.0.59
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -0,0 +1,37 @@
1
+ import os.path
2
+ import unittest
3
+
4
+ import ddt
5
+ from iker.common.utils.dtutils import dt_parse_iso
6
+
7
+ from plexus.common.utils.jsonutils import json_dumps, json_loads
8
+ from plexus.common.utils.jsonutils import read_chunked_jsonl
9
+ from testenv import resources_directory
10
+
11
+
12
+ @ddt.ddt
13
+ class JsonUtilsTest(unittest.TestCase):
14
+
15
+ def test_json_loads_dumps(self):
16
+ data = {
17
+ "int": 123,
18
+ "float": 123.456,
19
+ "string": "dummy_string",
20
+ "boolean": True,
21
+ "null": None,
22
+ "datetime": dt_parse_iso("2024-01-01T00:00:00.000000+00:00"),
23
+ "list": [123, 123.456, "dummy_string", True, None, dt_parse_iso("2024-01-01T00:00:00.000000+00:00")],
24
+ "dict": {
25
+ "int": 123,
26
+ "float": 123.456,
27
+ "string": "dummy_string",
28
+ "boolean": True,
29
+ "null": None,
30
+ "datetime": dt_parse_iso("2024-01-01T00:00:00.000000+00:00"),
31
+ },
32
+ }
33
+ self.assertEqual(data, json_loads(json_dumps(data)))
34
+
35
+ def test_read_chunked_jsonl(self):
36
+ for data, path in read_chunked_jsonl(str(resources_directory / "unittest" / "jsonutils" / "dummy.{{}}.jsonl")):
37
+ self.assertEqual(data["file"], os.path.basename(path))
@@ -6,6 +6,7 @@ import unittest
6
6
 
7
7
  import ddt
8
8
  from iker.common.utils.dtutils import dt_parse_iso
9
+ from iker.common.utils.iterutils import last
9
10
  from iker.common.utils.jsonutils import JsonObject
10
11
 
11
12
  from plexus.common.utils.tagutils import MutableTagset, Tag, TagCache, Tagset
@@ -25,8 +26,19 @@ class TagUtilsTest(unittest.TestCase):
25
26
  for tag in tagset:
26
27
  self.assertIn(tag, tagset)
27
28
  self.assertIn(tag.name, tagset)
28
- self.assertEqual(tag.name, tagset.get(tag).name)
29
- self.assertEqual(tag.name, tagset.get(tag.name).name)
29
+ self.assertEqual(tag, tagset.get(tag))
30
+ self.assertEqual(tag.name, tagset.get_bound(tag).name)
31
+ self.assertEqual(tag, tagset.get_bound(tag).unbind())
32
+
33
+ if tag.parent_tag_name is not None:
34
+ self.assertIn(tag.parent_tag_name, tagset)
35
+ self.assertEqual(tag.parent_tag_name, tagset.get(tag.parent_tag_name).name)
36
+ self.assertEqual(last(tagset.parent_tags(tag)), tagset.get(tag.parent_tag_name))
37
+
38
+ for child_tag in tagset.child_tags(tag):
39
+ self.assertIn(child_tag, tagset)
40
+ self.assertIn(child_tag.name, tagset)
41
+ self.assertIn(tag, tagset.parent_tags(child_tag))
30
42
 
31
43
  markdown = render_tagset_markdown_readme(tagset)
32
44
  self.assertIsInstance(markdown, str)
@@ -84,17 +96,20 @@ class TagUtilsTest(unittest.TestCase):
84
96
  tagset.validate(tag, props, raise_on_error=True)
85
97
 
86
98
  def test_tag_cache(self):
87
- tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
88
- tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
89
- tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
90
-
91
- tags = [
99
+ tag_names = [
92
100
  "dummy:foo",
93
101
  "dummy:bar",
94
102
  "dummy:baz",
95
103
  "dummy:qux",
96
104
  ]
97
105
 
106
+ tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
107
+ tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
108
+ tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
109
+
110
+ tags_count = len(tag_names)
111
+ tagset_tags_count = sum(1 for tag_name in tag_names if tag_name in tagset)
112
+
98
113
  cache = TagCache()
99
114
 
100
115
  self.assertEqual(cache.file_path, tag_cache_file_path())
@@ -109,72 +124,85 @@ class TagUtilsTest(unittest.TestCase):
109
124
 
110
125
  target_cache = cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
111
126
 
112
- tags_count = 1000
127
+ tag_records_count = 1000
113
128
 
114
- for i in range(tags_count):
129
+ for i in range(tag_records_count):
130
+ tag_name = tag_names[i % len(tag_names)]
131
+ tag = tagset.get_bound(tag_name) or tag_name
115
132
  target_cache.add_ranged_tag(
116
133
  dt_parse_iso("2020-01-01T00:00:00+00:00") + datetime.timedelta(seconds=i),
117
134
  dt_parse_iso("2020-01-02T00:00:00+00:00") + datetime.timedelta(seconds=i + 1),
118
- tags[i % len(tags)],
135
+ tag,
119
136
  )
120
137
 
121
- self.assertEqual(len(list(target_cache.iter_tags())), tags_count)
122
- self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset]))), tags_count // 2)
123
- self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))), tags_count // 2)
124
- self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))), tags_count // 2)
138
+ self.assertEqual(len(list(target_cache.iter_tags())), tag_records_count)
139
+ self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset]))),
140
+ tag_records_count * tagset_tags_count // tags_count)
141
+ self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
142
+ tag_records_count * (tags_count - tagset_tags_count) // tags_count)
143
+ self.assertEqual(len(list(target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
144
+ tag_records_count * tagset_tags_count // tags_count)
125
145
  self.assertEqual(len(list(target_cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
126
146
  dt_parse_iso("2020-01-01T00:01:00+00:00")))),
127
147
  60 + 1)
128
148
  self.assertEqual(len(list(target_cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
129
149
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
130
150
  tag_prefix="dummy:foo"))),
131
- 60 // len(tags) + 1)
151
+ 60 // tags_count + 1)
132
152
  self.assertEqual(len(list(target_cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
133
153
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
134
154
  tag_prefix="dummy:bar"))),
135
- 60 // len(tags))
155
+ 60 // tags_count)
136
156
  self.assertEqual(len(list(target_cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
137
157
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
138
158
  tag_prefix="dummy"))),
139
159
  60 + 1)
140
160
  self.assertEqual(len(list(target_cache.iter_tags(tag_prefix="dummy:foo"))),
141
- tags_count // len(tags))
161
+ tag_records_count // tags_count)
142
162
  self.assertEqual(len(list(target_cache.iter_tags(tag_prefix="dummy:bar"))),
143
- tags_count // len(tags))
144
- self.assertEqual(len(list(target_cache.iter_tags(tag_prefix="dummy"))), tags_count)
145
-
146
- self.assertEqual(len(list(cache.iter_tags())), tags_count)
147
- self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset]))), tags_count // 2)
148
- self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset], tagset_inverted=True))), tags_count // 2)
149
- self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset], tagset_inverted=False))), tags_count // 2)
163
+ tag_records_count // tags_count)
164
+ self.assertEqual(len(list(target_cache.iter_tags(tag_prefix="dummy"))), tag_records_count)
165
+
166
+ self.assertEqual(len(list(cache.iter_tags())), tag_records_count)
167
+ self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset]))), tag_records_count // 2)
168
+ self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
169
+ tag_records_count * (tags_count - tagset_tags_count) // tags_count)
170
+ self.assertEqual(len(list(cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
171
+ tag_records_count * tagset_tags_count // tags_count)
150
172
  self.assertEqual(len(list(cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
151
173
  dt_parse_iso("2020-01-01T00:01:00+00:00")))),
152
174
  60 + 1)
153
175
  self.assertEqual(len(list(cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
154
176
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
155
177
  tag_prefix="dummy:foo"))),
156
- 60 // len(tags) + 1)
178
+ 60 // tags_count + 1)
157
179
  self.assertEqual(len(list(cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
158
180
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
159
181
  tag_prefix="dummy:bar"))),
160
- 60 // len(tags))
182
+ 60 // tags_count)
161
183
  self.assertEqual(len(list(cache.iter_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"),
162
184
  dt_parse_iso("2020-01-01T00:01:00+00:00"),
163
185
  tag_prefix="dummy"))),
164
186
  60 + 1)
165
- self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy:foo"))), tags_count // len(tags))
166
- self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy:bar"))), tags_count // len(tags))
167
- self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy"))), tags_count)
187
+ self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy:foo"))), tag_records_count // tags_count)
188
+ self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy:bar"))), tag_records_count // tags_count)
189
+ self.assertEqual(len(list(cache.iter_tags(tag_prefix="dummy"))), tag_records_count)
168
190
 
169
191
  target_cache.remove_tags(dt_parse_iso("2020-01-01T00:00:00+00:00"), dt_parse_iso("2020-01-01T00:01:00+00:00"))
170
192
 
171
- self.assertEqual(len(list(target_cache.iter_tags())), tags_count - (60 + 1))
172
- self.assertEqual(len(list(cache.iter_tags())), tags_count - (60 + 1))
193
+ self.assertEqual(len(list(target_cache.iter_tags())), tag_records_count - (60 + 1))
194
+ self.assertEqual(len(list(cache.iter_tags())), tag_records_count - (60 + 1))
173
195
 
174
196
  target_cache.remove_tags(tagsets=[tagset], tagset_inverted=True)
175
197
 
176
- self.assertEqual(len(list(target_cache.iter_tags())), tags_count // 2 - (60 // 2 + 1))
177
- self.assertEqual(len(list(cache.iter_tags())), tags_count // 2 - (60 // 2 + 1))
198
+ self.assertEqual(
199
+ len(list(target_cache.iter_tags())),
200
+ tag_records_count * tagset_tags_count // tags_count - (60 * tagset_tags_count // tags_count + 1),
201
+ )
202
+ self.assertEqual(
203
+ len(list(cache.iter_tags())),
204
+ tag_records_count * tagset_tags_count // tags_count - (60 * tagset_tags_count // tags_count + 1),
205
+ )
178
206
 
179
207
  target_cache.remove_tags()
180
208
 
@@ -182,17 +210,27 @@ class TagUtilsTest(unittest.TestCase):
182
210
  self.assertEqual(len(list(cache.iter_tags())), 0)
183
211
 
184
212
  def test_tag_cache__multithread(self):
185
- tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
186
- tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
187
- tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
188
-
189
- tags = [
213
+ tag_names = [
190
214
  "dummy:foo",
191
215
  "dummy:bar",
192
216
  "dummy:baz",
193
217
  "dummy:qux",
194
218
  ]
195
219
 
220
+ old_tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
221
+ old_tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
222
+ old_tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
223
+
224
+ new_tagset = MutableTagset(namespace="tagset", version="1.1.0", desc="Updated dummy tagset for testing")
225
+ new_tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
226
+ new_tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
227
+ new_tagset.add(Tag(name="dummy:baz", desc="Updated dummy tag for testing"))
228
+ new_tagset.remove("dummy:bar")
229
+
230
+ tags_count = len(tag_names)
231
+ tagset_tags_count = sum(1 for tag_name in tag_names if tag_name in old_tagset or tag_name in new_tagset)
232
+ old_tagset_tags_count = sum(1 for tag_name in tag_names if tag_name in old_tagset)
233
+
196
234
  with tempfile.TemporaryDirectory() as temp_directory:
197
235
  temp_directory = pathlib.Path(temp_directory)
198
236
 
@@ -223,14 +261,16 @@ class TagUtilsTest(unittest.TestCase):
223
261
  start_barrier = threading.Barrier(total_threads_count)
224
262
 
225
263
  def worker(tid):
226
- tag_cache = target_caches[tid % target_caches_count]
264
+ target_cache = target_caches[tid % target_caches_count]
227
265
 
228
266
  start_barrier.wait()
229
267
  for i in range(tasks_count_per_thread):
230
- tag_cache.add_ranged_tag(
268
+ tag_name = tag_names[i % len(tag_names)]
269
+ tag = old_tagset.get_bound(tag_name) or new_tagset.get_bound(tag_name) or tag_name
270
+ target_cache.add_ranged_tag(
231
271
  dt_parse_iso("2020-01-01T00:00:00+00:00") + datetime.timedelta(seconds=i),
232
272
  dt_parse_iso("2020-01-02T00:00:00+00:00") + datetime.timedelta(seconds=i + 1),
233
- tags[i % len(tags)],
273
+ tag,
234
274
  )
235
275
 
236
276
  threads = [threading.Thread(target=worker, args=(tid,)) for tid in range(total_threads_count)]
@@ -242,24 +282,35 @@ class TagUtilsTest(unittest.TestCase):
242
282
  for target_cache in target_caches:
243
283
  self.assertEqual(len(list(target_cache.iter_tags())), tasks_count_per_target_cache)
244
284
  self.assertEqual(len(list(target_cache.iter_tags(tag_prefix="dummy:bar"))),
245
- tasks_count_per_target_cache // len(tags))
285
+ tasks_count_per_target_cache // tags_count)
286
+ self.assertEqual(len(list(target_cache.iter_tags(tagset_namespace="tagset"))),
287
+ tasks_count_per_target_cache * tagset_tags_count // tags_count)
288
+ self.assertEqual(len(list(target_cache.iter_tags(tagset_namespace="tagset", tagset_version="1.0.0"))),
289
+ tasks_count_per_target_cache * old_tagset_tags_count // tags_count)
246
290
 
247
291
  self.assertEqual(len(list(cache.iter_tag_and_targets())), total_tasks_count)
248
292
  self.assertEqual(len(list(cache.iter_tag_and_targets(tag_prefix="dummy:bar"))),
249
- total_tasks_count // len(tags))
293
+ total_tasks_count // tags_count)
294
+ self.assertEqual(len(list(cache.iter_tag_and_targets(tagset_namespace="tagset"))),
295
+ total_tasks_count * tagset_tags_count // tags_count)
296
+ self.assertEqual(len(list(cache.iter_tag_and_targets(tagset_namespace="tagset", tagset_version="1.0.0"))),
297
+ total_tasks_count * old_tagset_tags_count // tags_count)
250
298
 
251
299
  def test_tag_cache__clone(self):
252
- tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
253
- tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
254
- tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
255
-
256
- tags = [
300
+ tag_names = [
257
301
  "dummy:foo",
258
302
  "dummy:bar",
259
303
  "dummy:baz",
260
304
  "dummy:qux",
261
305
  ]
262
306
 
307
+ tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
308
+ tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
309
+ tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
310
+
311
+ tags_count = len(tag_names)
312
+ tagset_tags_count = sum(1 for tag_name in tag_names if tag_name in tagset)
313
+
263
314
  with tempfile.TemporaryDirectory() as temp_directory:
264
315
  temp_directory = pathlib.Path(temp_directory)
265
316
 
@@ -274,46 +325,55 @@ class TagUtilsTest(unittest.TestCase):
274
325
 
275
326
  src_target_cache = src_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
276
327
 
277
- tags_count = 1000
328
+ tag_records_count = 1000
278
329
 
279
- for i in range(tags_count):
280
- src_target_cache.add_tag(tags[i % len(tags)])
330
+ for i in range(tag_records_count):
331
+ tag_name = tag_names[i % len(tag_names)]
332
+ tag = tagset.get_bound(tag_name) or tag_name
333
+ src_target_cache.add_tag(tag)
281
334
 
282
335
  dst_cache = TagCache(file_path=temp_directory / "dst_tag_cache.db")
283
336
 
284
337
  TagCache.copy_to(src_cache, dst_cache)
285
338
 
286
- for i in range(tags_count):
287
- src_target_cache.add_tag(tags[i % len(tags)])
339
+ for i in range(tag_records_count):
340
+ tag_name = tag_names[i % len(tag_names)]
341
+ tag = tagset.get_bound(tag_name) or tag_name
342
+ src_target_cache.add_tag(tag)
288
343
 
289
344
  dst_target_cache = dst_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
290
345
 
291
- self.assertEqual(len(list(src_target_cache.iter_tags())), tags_count * 2)
292
- self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))), tags_count * 2 // 2)
346
+ self.assertEqual(len(list(src_target_cache.iter_tags())), tag_records_count * 2)
347
+ self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))),
348
+ tag_records_count * 2 * tagset_tags_count // tags_count)
293
349
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
294
- tags_count * 2 // 2)
350
+ tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
295
351
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
296
- tags_count * 2 // 2)
352
+ tag_records_count * 2 * tagset_tags_count // tags_count)
297
353
 
298
- self.assertEqual(len(list(dst_target_cache.iter_tags())), tags_count)
299
- self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))), tags_count // 2)
354
+ self.assertEqual(len(list(dst_target_cache.iter_tags())), tag_records_count)
355
+ self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))),
356
+ tag_records_count * tagset_tags_count // tags_count)
300
357
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
301
- tags_count // 2)
358
+ tag_records_count * (tags_count - tagset_tags_count) // tags_count)
302
359
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
303
- tags_count // 2)
360
+ tag_records_count * tagset_tags_count // tags_count)
304
361
 
305
362
  def test_tag_cache__clone_same_file(self):
306
- tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
307
- tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
308
- tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
309
-
310
- tags = [
363
+ tag_names = [
311
364
  "dummy:foo",
312
365
  "dummy:bar",
313
366
  "dummy:baz",
314
367
  "dummy:qux",
315
368
  ]
316
369
 
370
+ tagset = MutableTagset(namespace="tagset", version="1.0.0", desc="A dummy tagset for testing")
371
+ tagset.add(Tag(name="dummy:foo", desc="A dummy tag for testing"))
372
+ tagset.add(Tag(name="dummy:bar", desc="Another dummy tag for testing"))
373
+
374
+ tags_count = len(tag_names)
375
+ tagset_tags_count = sum(1 for tag_name in tag_names if tag_name in tagset)
376
+
317
377
  # Clone to the same file path should not cause any issue, and the cloned cache should be able to read
318
378
  # the tags added to the source cache after cloning.
319
379
  with tempfile.TemporaryDirectory() as temp_directory:
@@ -330,33 +390,39 @@ class TagUtilsTest(unittest.TestCase):
330
390
 
331
391
  src_target_cache = src_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
332
392
 
333
- tags_count = 1000
393
+ tag_records_count = 1000
334
394
 
335
- for i in range(tags_count):
336
- src_target_cache.add_tag(tags[i % len(tags)])
395
+ for i in range(tag_records_count):
396
+ tag_name = tag_names[i % len(tag_names)]
397
+ tag = tagset.get_bound(tag_name) or tag_name
398
+ src_target_cache.add_tag(tag)
337
399
 
338
400
  dst_cache = TagCache(file_path=temp_directory / "tag_cache.db")
339
401
 
340
402
  TagCache.copy_to(src_cache, dst_cache)
341
403
 
342
- for i in range(tags_count):
343
- src_target_cache.add_tag(tags[i % len(tags)])
404
+ for i in range(tag_records_count):
405
+ tag_name = tag_names[i % len(tag_names)]
406
+ tag = tagset.get_bound(tag_name) or tag_name
407
+ src_target_cache.add_tag(tag)
344
408
 
345
409
  dst_target_cache = dst_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
346
410
 
347
- self.assertEqual(len(list(src_target_cache.iter_tags())), tags_count * 2)
348
- self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))), tags_count * 2 // 2)
411
+ self.assertEqual(len(list(src_target_cache.iter_tags())), tag_records_count * 2)
412
+ self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))),
413
+ tag_records_count * 2 * tagset_tags_count // tags_count)
349
414
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
350
- tags_count * 2 // 2)
415
+ tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
351
416
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
352
- tags_count * 2 // 2)
417
+ tag_records_count * 2 * tagset_tags_count // tags_count)
353
418
 
354
- self.assertEqual(len(list(dst_target_cache.iter_tags())), tags_count * 2)
355
- self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))), tags_count * 2 // 2)
419
+ self.assertEqual(len(list(dst_target_cache.iter_tags())), tag_records_count * 2)
420
+ self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))),
421
+ tag_records_count * 2 * tagset_tags_count // tags_count)
356
422
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
357
- tags_count * 2 // 2)
423
+ tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
358
424
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
359
- tags_count * 2 // 2)
425
+ tag_records_count * 2 * tagset_tags_count // tags_count)
360
426
 
361
427
  with tempfile.TemporaryDirectory() as temp_directory:
362
428
  temp_directory = pathlib.Path(temp_directory)
@@ -372,30 +438,36 @@ class TagUtilsTest(unittest.TestCase):
372
438
 
373
439
  src_target_cache = src_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
374
440
 
375
- tags_count = 1000
441
+ tag_records_count = 1000
376
442
 
377
- for i in range(tags_count):
378
- src_target_cache.add_tag(tags[i % len(tags)])
443
+ for i in range(tag_records_count):
444
+ tag_name = tag_names[i % len(tag_names)]
445
+ tag = tagset.get_bound(tag_name) or tag_name
446
+ src_target_cache.add_tag(tag)
379
447
 
380
448
  dst_cache = src_cache
381
449
 
382
450
  TagCache.copy_to(src_cache, dst_cache)
383
451
 
384
- for i in range(tags_count):
385
- src_target_cache.add_tag(tags[i % len(tags)])
452
+ for i in range(tag_records_count):
453
+ tag_name = tag_names[i % len(tag_names)]
454
+ tag = tagset.get_bound(tag_name) or tag_name
455
+ src_target_cache.add_tag(tag)
386
456
 
387
457
  dst_target_cache = dst_cache.with_target("awesome_tagger/20200101_000000/dummy_vehicle/0")
388
458
 
389
- self.assertEqual(len(list(src_target_cache.iter_tags())), tags_count * 2)
390
- self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))), tags_count * 2 // 2)
459
+ self.assertEqual(len(list(src_target_cache.iter_tags())), tag_records_count * 2)
460
+ self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset]))),
461
+ tag_records_count * 2 * tagset_tags_count // tags_count)
391
462
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
392
- tags_count * 2 // 2)
463
+ tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
393
464
  self.assertEqual(len(list(src_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
394
- tags_count * 2 // 2)
465
+ tag_records_count * 2 * tagset_tags_count // tags_count)
395
466
 
396
- self.assertEqual(len(list(dst_target_cache.iter_tags())), tags_count * 2)
397
- self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))), tags_count * 2 // 2)
467
+ self.assertEqual(len(list(dst_target_cache.iter_tags())), tag_records_count * 2)
468
+ self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset]))),
469
+ tag_records_count * 2 * tagset_tags_count // tags_count)
398
470
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=True))),
399
- tags_count * 2 // 2)
471
+ tag_records_count * 2 * (tags_count - tagset_tags_count) // tags_count)
400
472
  self.assertEqual(len(list(dst_target_cache.iter_tags(tagsets=[tagset], tagset_inverted=False))),
401
- tags_count * 2 // 2)
473
+ tag_records_count * 2 * tagset_tags_count // tags_count)
@@ -1,15 +0,0 @@
1
- import os.path
2
- import unittest
3
-
4
- import ddt
5
-
6
- from plexus.common.utils.jsonutils import read_chunked_jsonl
7
- from testenv import resources_directory
8
-
9
-
10
- @ddt.ddt
11
- class JsonUtilsTest(unittest.TestCase):
12
-
13
- def test_read_chunked_jsonl(self):
14
- for data, path in read_chunked_jsonl(str(resources_directory / "unittest" / "jsonutils" / "dummy.{{}}.jsonl")):
15
- self.assertEqual(data["file"], os.path.basename(path))