plexus-python-common 1.0.49__tar.gz → 1.0.50__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.
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/PKG-INFO +1 -1
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/tagutils.py +306 -24
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/tagutils_test.py +34 -3
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/.editorconfig +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/.github/workflows/pr.yml +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/.github/workflows/push.yml +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/.gitignore +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/MANIFEST.in +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/README.md +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/VERSION +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/pyproject.toml +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/0-dummy +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/1-dummy +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/2-dummy +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.txt +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/setup.cfg +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/setup.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMFile.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMNode.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMTags.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMWay.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/pose.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/proj.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/resources/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/resources/tags/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/resources/tags/universal.tagset.yaml +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/apiutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/bagutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/config.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/datautils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/dockerutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/jsonutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/ormutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/pathutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/s3utils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/sqlutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/strutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/testutils.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/requires.txt +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus_python_common.egg-info/top_level.txt +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/carto/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/pose_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/proj_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/__init__.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/datautils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/strutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/utils/testutils_test.py +0 -0
- {plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/testenv.py +0 -0
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/tagutils.py
RENAMED
|
@@ -13,7 +13,7 @@ import sqlalchemy as sa
|
|
|
13
13
|
import sqlalchemy.dialects.sqlite as sa_sqlite
|
|
14
14
|
import sqlalchemy.orm as sa_orm
|
|
15
15
|
from iker.common.utils.dbutils import ConnectionMaker
|
|
16
|
-
from iker.common.utils.funcutils import singleton
|
|
16
|
+
from iker.common.utils.funcutils import memorized, singleton
|
|
17
17
|
from iker.common.utils.iterutils import dicttree
|
|
18
18
|
from iker.common.utils.iterutils import dicttree_add, dicttree_remove
|
|
19
19
|
from iker.common.utils.iterutils import dicttree_children, dicttree_lineage, dicttree_subtree
|
|
@@ -24,15 +24,17 @@ from iker.common.utils.strutils import is_blank
|
|
|
24
24
|
from sqlmodel import Field, SQLModel
|
|
25
25
|
|
|
26
26
|
from plexus.common.resources.tags import predefined_tagset_specs
|
|
27
|
+
from plexus.common.utils.datautils import validate_bag_name, validate_dt_timezone
|
|
27
28
|
from plexus.common.utils.datautils import validate_colon_tag, validate_snake_case, validate_vehicle_name
|
|
28
|
-
from plexus.common.utils.datautils import validate_dt_timezone
|
|
29
29
|
from plexus.common.utils.ormutils import SequenceModelMixinProtocol
|
|
30
30
|
from plexus.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
|
|
31
31
|
from plexus.common.utils.sqlutils import escape_sql_like
|
|
32
32
|
|
|
33
33
|
__all__ = [
|
|
34
34
|
"TagRecord",
|
|
35
|
+
"BagTagRecord",
|
|
35
36
|
"TagRecordTable",
|
|
37
|
+
"BagTagRecordTable",
|
|
36
38
|
"RichDesc",
|
|
37
39
|
"Tag",
|
|
38
40
|
"Tagset",
|
|
@@ -42,6 +44,7 @@ __all__ = [
|
|
|
42
44
|
"render_tagset_markdown_readme",
|
|
43
45
|
"tag_cache_file_path",
|
|
44
46
|
"TagCache",
|
|
47
|
+
"tag_cache",
|
|
45
48
|
]
|
|
46
49
|
|
|
47
50
|
BaseModel = make_base_model()
|
|
@@ -61,7 +64,7 @@ class TagRecord(BaseModel):
|
|
|
61
64
|
description="End datetime of the tag record",
|
|
62
65
|
)
|
|
63
66
|
tag: str = Field(
|
|
64
|
-
sa_column=sa.Column(sa_sqlite.
|
|
67
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False),
|
|
65
68
|
description="Tag name",
|
|
66
69
|
)
|
|
67
70
|
props: JsonType | None = Field(
|
|
@@ -91,7 +94,73 @@ class TagRecord(BaseModel):
|
|
|
91
94
|
@pdt.model_validator(mode="after")
|
|
92
95
|
def validate_begin_dt_end_dt(self) -> Self:
|
|
93
96
|
if self.begin_dt > self.end_dt:
|
|
94
|
-
raise ValueError(f"
|
|
97
|
+
raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
@pdt.field_validator("tag", mode="after")
|
|
101
|
+
@classmethod
|
|
102
|
+
def validate_tag(cls, v: str) -> str:
|
|
103
|
+
validate_colon_tag(v)
|
|
104
|
+
return v
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class BagTagRecord(BaseModel):
|
|
108
|
+
vehicle_name: str = Field(
|
|
109
|
+
sa_column=sa.Column(sa_sqlite.TEXT, nullable=False),
|
|
110
|
+
description="Vehicle name associated with the tag record",
|
|
111
|
+
)
|
|
112
|
+
bag_name: str = Field(
|
|
113
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False),
|
|
114
|
+
description="Name of the bag associated with the tag record",
|
|
115
|
+
)
|
|
116
|
+
begin_offset: int = Field(
|
|
117
|
+
sa_column=sa.Column(sa_sqlite.INTEGER, nullable=False),
|
|
118
|
+
description="Begin offset (in microseconds) of the tag record within the bag",
|
|
119
|
+
)
|
|
120
|
+
end_offset: int = Field(
|
|
121
|
+
sa_column=sa.Column(sa_sqlite.INTEGER, nullable=False),
|
|
122
|
+
description="End offset (in microseconds) of the tag record within the bag",
|
|
123
|
+
)
|
|
124
|
+
tag: str = Field(
|
|
125
|
+
sa_column=sa.Column(sa_sqlite.VARCHAR(256), nullable=False),
|
|
126
|
+
description="Tag name",
|
|
127
|
+
)
|
|
128
|
+
props: JsonType | None = Field(
|
|
129
|
+
sa_column=sa.Column(sa_sqlite.TEXT, nullable=True),
|
|
130
|
+
default=None,
|
|
131
|
+
description="Additional properties of the tag record in JSON format",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@pdt.field_validator("vehicle_name", mode="after")
|
|
135
|
+
@classmethod
|
|
136
|
+
def validate_vehicle_name(cls, v: str) -> str:
|
|
137
|
+
validate_vehicle_name(v)
|
|
138
|
+
return v
|
|
139
|
+
|
|
140
|
+
@pdt.field_validator("bag_name", mode="after")
|
|
141
|
+
@classmethod
|
|
142
|
+
def validate_bag_name(cls, v: str) -> str:
|
|
143
|
+
validate_bag_name(v)
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
@pdt.field_validator("begin_offset", mode="after")
|
|
147
|
+
@classmethod
|
|
148
|
+
def validate_begin_offset(cls, v: int) -> int:
|
|
149
|
+
if v < 0:
|
|
150
|
+
raise ValueError(f"begin_offset '{v}' is negative")
|
|
151
|
+
return v
|
|
152
|
+
|
|
153
|
+
@pdt.field_validator("end_offset", mode="after")
|
|
154
|
+
@classmethod
|
|
155
|
+
def validate_end_offset(cls, v: int) -> int:
|
|
156
|
+
if v < 0:
|
|
157
|
+
raise ValueError(f"end_offset '{v}' is negative")
|
|
158
|
+
return v
|
|
159
|
+
|
|
160
|
+
@pdt.model_validator(mode="after")
|
|
161
|
+
def validate_begin_offset_end_offset(self) -> Self:
|
|
162
|
+
if self.begin_offset > self.end_offset:
|
|
163
|
+
raise ValueError(f"begin offset '{self.begin_offset}' is greater than end offset '{self.end_offset}'")
|
|
95
164
|
return self
|
|
96
165
|
|
|
97
166
|
@pdt.field_validator("tag", mode="after")
|
|
@@ -105,6 +174,10 @@ class TagRecordTable(TagRecord, make_sequence_model_mixin("sqlite"), table=True)
|
|
|
105
174
|
__tablename__ = "tag_record"
|
|
106
175
|
|
|
107
176
|
|
|
177
|
+
class BagTagRecordTable(BagTagRecord, make_sequence_model_mixin("sqlite"), table=True):
|
|
178
|
+
__tablename__ = "bag_tag_record"
|
|
179
|
+
|
|
180
|
+
|
|
108
181
|
if typing.TYPE_CHECKING:
|
|
109
182
|
class TagRecordTable(SQLModel, SequenceModelMixinProtocol):
|
|
110
183
|
vehicle_name: sa_orm.Mapped[str] = ...
|
|
@@ -400,7 +473,21 @@ class TagCache(object):
|
|
|
400
473
|
tag_pattern: str | None = None,
|
|
401
474
|
*,
|
|
402
475
|
tagsets: Sequence[Tagset] | None = None,
|
|
403
|
-
|
|
476
|
+
tagset_inverted: bool = False,
|
|
477
|
+
) -> Generator[TagRecordTable, None, None]:
|
|
478
|
+
"""
|
|
479
|
+
Query tag records in the cache with optional filters.
|
|
480
|
+
|
|
481
|
+
:param vehicle_name: Filter by vehicle name (exact match)
|
|
482
|
+
:param begin_time: Filter by begin time (inclusive)
|
|
483
|
+
:param end_time: Filter by end time (inclusive)
|
|
484
|
+
:param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
|
|
485
|
+
with "dummy_tag:")
|
|
486
|
+
:param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
|
|
487
|
+
:param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
|
|
488
|
+
tagsets)
|
|
489
|
+
:return: Generator of ``TagRecordTable`` instances that match the filters
|
|
490
|
+
"""
|
|
404
491
|
with self.conn_maker.make_session() as session:
|
|
405
492
|
query = session.query(TagRecordTable)
|
|
406
493
|
if vehicle_name:
|
|
@@ -411,16 +498,62 @@ class TagCache(object):
|
|
|
411
498
|
query = query.filter(TagRecordTable.begin_dt <= end_time)
|
|
412
499
|
if tag_pattern:
|
|
413
500
|
query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
|
|
501
|
+
if tagsets:
|
|
502
|
+
if tagset_inverted:
|
|
503
|
+
query = query.filter(
|
|
504
|
+
TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
505
|
+
else:
|
|
506
|
+
query = query.filter(
|
|
507
|
+
TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
414
508
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
509
|
+
yield from query.all()
|
|
510
|
+
|
|
511
|
+
def query_bag_tag(
|
|
512
|
+
self,
|
|
513
|
+
vehicle_name: str | None = None,
|
|
514
|
+
bag_name: str | None = None,
|
|
515
|
+
begin_offset: int | None = None,
|
|
516
|
+
end_offset: int | None = None,
|
|
517
|
+
tag_pattern: str | None = None,
|
|
518
|
+
*,
|
|
519
|
+
tagsets: Sequence[Tagset] | None = None,
|
|
520
|
+
tagset_inverted: bool = False,
|
|
521
|
+
) -> Generator[BagTagRecordTable, None, None]:
|
|
522
|
+
"""
|
|
523
|
+
Query bag tag records in the cache with optional filters.
|
|
524
|
+
|
|
525
|
+
:param vehicle_name: Filter by vehicle name (exact match)
|
|
526
|
+
:param bag_name: Filter by bag name (exact match)
|
|
527
|
+
:param begin_offset: Filter by begin offset (inclusive)
|
|
528
|
+
:param end_offset: Filter by end offset (inclusive)
|
|
529
|
+
:param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
|
|
530
|
+
with "dummy_tag:")
|
|
531
|
+
:param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
|
|
532
|
+
:param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
|
|
533
|
+
tagsets)
|
|
534
|
+
:return: Generator of ``BagTagRecordTable`` instances that match the filters
|
|
535
|
+
"""
|
|
536
|
+
with self.conn_maker.make_session() as session:
|
|
537
|
+
query = session.query(BagTagRecordTable)
|
|
538
|
+
if vehicle_name:
|
|
539
|
+
query = query.filter(BagTagRecordTable.vehicle_name == vehicle_name)
|
|
540
|
+
if bag_name:
|
|
541
|
+
query = query.filter(BagTagRecordTable.bag_name == bag_name)
|
|
542
|
+
if begin_offset:
|
|
543
|
+
query = query.filter(BagTagRecordTable.end_offset >= begin_offset)
|
|
544
|
+
if end_offset:
|
|
545
|
+
query = query.filter(BagTagRecordTable.begin_offset <= end_offset)
|
|
546
|
+
if tag_pattern:
|
|
547
|
+
query = query.filter(BagTagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
|
|
548
|
+
if tagsets:
|
|
549
|
+
if tagset_inverted:
|
|
550
|
+
query = query.filter(
|
|
551
|
+
BagTagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
552
|
+
else:
|
|
553
|
+
query = query.filter(
|
|
554
|
+
BagTagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
420
555
|
|
|
421
|
-
|
|
422
|
-
yield from (db_tag_record for db_tag_record in self.query()
|
|
423
|
-
if all(db_tag_record.tag not in tagset for tagset in tagsets))
|
|
556
|
+
yield from query.all()
|
|
424
557
|
|
|
425
558
|
def add(
|
|
426
559
|
self,
|
|
@@ -430,6 +563,16 @@ class TagCache(object):
|
|
|
430
563
|
tag: str | Tag,
|
|
431
564
|
props: JsonType | None = None,
|
|
432
565
|
) -> Self:
|
|
566
|
+
"""
|
|
567
|
+
Add a tag record to the cache.
|
|
568
|
+
|
|
569
|
+
:param vehicle_name: Vehicle name associated with the tag record
|
|
570
|
+
:param begin_time: Begin datetime of the tag record
|
|
571
|
+
:param end_time: End datetime of the tag record
|
|
572
|
+
:param tag: Tag name or Tag instance to be added (if Tag instance is provided, its name will be used)
|
|
573
|
+
:param props: Additional properties of the tag record in JSON format (optional)
|
|
574
|
+
:return: Self instance for chaining
|
|
575
|
+
"""
|
|
433
576
|
with self.conn_maker.make_session() as session:
|
|
434
577
|
tag_record = TagRecord(
|
|
435
578
|
vehicle_name=vehicle_name,
|
|
@@ -443,17 +586,132 @@ class TagCache(object):
|
|
|
443
586
|
|
|
444
587
|
return self
|
|
445
588
|
|
|
446
|
-
def
|
|
589
|
+
def add_bag_tag(
|
|
590
|
+
self,
|
|
591
|
+
vehicle_name: str,
|
|
592
|
+
bag_name: str,
|
|
593
|
+
begin_offset: int,
|
|
594
|
+
end_offset: int,
|
|
595
|
+
tag: str | Tag,
|
|
596
|
+
props: JsonType | None = None,
|
|
597
|
+
) -> Self:
|
|
598
|
+
"""
|
|
599
|
+
Add a bag tag record to the cache.
|
|
600
|
+
|
|
601
|
+
:param vehicle_name: Vehicle name associated with the tag record
|
|
602
|
+
:param bag_name: Name of the bag associated with the tag record
|
|
603
|
+
:param begin_offset: Begin offset (in microseconds) of the tag record within the bag (inclusive)
|
|
604
|
+
:param end_offset: End offset (in microseconds) of the tag record within the bag (inclusive)
|
|
605
|
+
:param tag: Tag name or Tag instance to be added (if Tag instance is provided, its name will be used)
|
|
606
|
+
:param props: Additional properties of the tag record in JSON format (optional)
|
|
607
|
+
:return: Self instance for chaining
|
|
608
|
+
"""
|
|
447
609
|
with self.conn_maker.make_session() as session:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
)
|
|
610
|
+
tag_record = BagTagRecord(
|
|
611
|
+
vehicle_name=vehicle_name,
|
|
612
|
+
bag_name=bag_name,
|
|
613
|
+
begin_offset=begin_offset,
|
|
614
|
+
end_offset=end_offset,
|
|
615
|
+
tag=tag.name if isinstance(tag, Tag) else tag,
|
|
616
|
+
props=props,
|
|
456
617
|
)
|
|
618
|
+
session.add(clone_sequence_model_instance(BagTagRecordTable, tag_record))
|
|
619
|
+
session.commit()
|
|
620
|
+
|
|
621
|
+
return self
|
|
622
|
+
|
|
623
|
+
def remove(
|
|
624
|
+
self,
|
|
625
|
+
vehicle_name: str | None = None,
|
|
626
|
+
begin_time: datetime.datetime | None = None,
|
|
627
|
+
end_time: datetime.datetime | None = None,
|
|
628
|
+
tag_pattern: str | None = None,
|
|
629
|
+
*,
|
|
630
|
+
tagsets: Sequence[Tagset] | None = None,
|
|
631
|
+
tagset_inverted: bool = False,
|
|
632
|
+
) -> Self:
|
|
633
|
+
"""
|
|
634
|
+
Remove tag records from the cache that match the specified filters.
|
|
635
|
+
|
|
636
|
+
:param vehicle_name: Filter by vehicle name (exact match)
|
|
637
|
+
:param begin_time: Filter by begin time (inclusive)
|
|
638
|
+
:param end_time: Filter by end time (inclusive)
|
|
639
|
+
:param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
|
|
640
|
+
with "dummy_tag:")
|
|
641
|
+
:param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
|
|
642
|
+
:param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
|
|
643
|
+
tagsets)
|
|
644
|
+
:return: Self instance for chaining
|
|
645
|
+
"""
|
|
646
|
+
with self.conn_maker.make_session() as session:
|
|
647
|
+
query = session.query(TagRecordTable)
|
|
648
|
+
if vehicle_name:
|
|
649
|
+
query = query.filter(TagRecordTable.vehicle_name == vehicle_name)
|
|
650
|
+
if begin_time:
|
|
651
|
+
query = query.filter(TagRecordTable.end_dt >= begin_time)
|
|
652
|
+
if end_time:
|
|
653
|
+
query = query.filter(TagRecordTable.begin_dt <= end_time)
|
|
654
|
+
if tag_pattern:
|
|
655
|
+
query = query.filter(TagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
|
|
656
|
+
if tagsets:
|
|
657
|
+
if tagset_inverted:
|
|
658
|
+
query = query.filter(
|
|
659
|
+
TagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
660
|
+
else:
|
|
661
|
+
query = query.filter(
|
|
662
|
+
TagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
663
|
+
|
|
664
|
+
query.delete()
|
|
665
|
+
session.commit()
|
|
666
|
+
|
|
667
|
+
return self
|
|
668
|
+
|
|
669
|
+
def remove_bag_tag(
|
|
670
|
+
self,
|
|
671
|
+
vehicle_name: str | None = None,
|
|
672
|
+
bag_name: str | None = None,
|
|
673
|
+
begin_offset: int | None = None,
|
|
674
|
+
end_offset: int | None = None,
|
|
675
|
+
tag_pattern: str | None = None,
|
|
676
|
+
*,
|
|
677
|
+
tagsets: Sequence[Tagset] | None = None,
|
|
678
|
+
tagset_inverted: bool = False,
|
|
679
|
+
) -> Self:
|
|
680
|
+
"""
|
|
681
|
+
Remove bag tag records from the cache that match the specified filters.
|
|
682
|
+
|
|
683
|
+
:param vehicle_name: Filter by vehicle name (exact match)
|
|
684
|
+
:param bag_name: Filter by bag name (exact match)
|
|
685
|
+
:param begin_offset: Filter by begin offset (inclusive)
|
|
686
|
+
:param end_offset: Filter by end offset (inclusive)
|
|
687
|
+
:param tag_pattern: Filter by tag name pattern (SQL LIKE syntax, e.g. "dummy_tag:%" to match all tags starting
|
|
688
|
+
with "dummy_tag:")
|
|
689
|
+
:param tagsets: Filter by tagsets (match tags that are in any of the specified tagsets)
|
|
690
|
+
:param tagset_inverted: Whether to invert the tagset filter (match tags that are NOT in any of the specified
|
|
691
|
+
tagsets)
|
|
692
|
+
:return: Self instance for chaining
|
|
693
|
+
"""
|
|
694
|
+
with self.conn_maker.make_session() as session:
|
|
695
|
+
query = session.query(BagTagRecordTable)
|
|
696
|
+
if vehicle_name:
|
|
697
|
+
query = query.filter(BagTagRecordTable.vehicle_name == vehicle_name)
|
|
698
|
+
if bag_name:
|
|
699
|
+
query = query.filter(BagTagRecordTable.bag_name == bag_name)
|
|
700
|
+
if begin_offset:
|
|
701
|
+
query = query.filter(BagTagRecordTable.end_offset >= begin_offset)
|
|
702
|
+
if end_offset:
|
|
703
|
+
query = query.filter(BagTagRecordTable.begin_offset <= end_offset)
|
|
704
|
+
if tag_pattern:
|
|
705
|
+
query = query.filter(BagTagRecordTable.tag.like(f"{escape_sql_like(tag_pattern)}%", escape="\\"))
|
|
706
|
+
if tagsets:
|
|
707
|
+
if tagset_inverted:
|
|
708
|
+
query = query.filter(
|
|
709
|
+
BagTagRecordTable.tag.notin_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
710
|
+
else:
|
|
711
|
+
query = query.filter(
|
|
712
|
+
BagTagRecordTable.tag.in_([tag_name for tagset in tagsets for tag_name in tagset.tag_names]))
|
|
713
|
+
|
|
714
|
+
query.delete()
|
|
457
715
|
session.commit()
|
|
458
716
|
|
|
459
717
|
return self
|
|
@@ -461,6 +719,7 @@ class TagCache(object):
|
|
|
461
719
|
def clear(self):
|
|
462
720
|
with self.conn_maker.make_session() as session:
|
|
463
721
|
session.execute(sa.delete(TagRecordTable))
|
|
722
|
+
session.execute(sa.delete(BagTagRecordTable))
|
|
464
723
|
session.commit()
|
|
465
724
|
|
|
466
725
|
def append_to(self, target_file_path: str, *, overwrite: bool = False):
|
|
@@ -479,6 +738,29 @@ class TagCache(object):
|
|
|
479
738
|
def clone_to(source: "TagCache", target: "TagCache"):
|
|
480
739
|
target.clear()
|
|
481
740
|
with source.conn_maker.make_session() as source_session, target.conn_maker.make_session() as target_session:
|
|
482
|
-
target_session.add_all(
|
|
483
|
-
|
|
741
|
+
target_session.add_all(
|
|
742
|
+
[clone_sequence_model_instance(TagRecordTable, db_tag_record, clear_meta_fields=True)
|
|
743
|
+
for db_tag_record in source_session.query(TagRecordTable).all()],
|
|
744
|
+
)
|
|
745
|
+
target_session.add_all(
|
|
746
|
+
[clone_sequence_model_instance(BagTagRecordTable, db_tag_record, clear_meta_fields=True)
|
|
747
|
+
for db_tag_record in source_session.query(BagTagRecordTable).all()],
|
|
748
|
+
)
|
|
484
749
|
target_session.commit()
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
@memorized
|
|
753
|
+
def tag_cache(identifier: str | None = None) -> TagCache:
|
|
754
|
+
"""
|
|
755
|
+
Get a ``TagCache`` instance associated with the given identifier. If the identifier is ``None``, return a
|
|
756
|
+
``TagCache`` instance associated with a default file path. Otherwise, validate the identifier as a snake case
|
|
757
|
+
string and return a ``TagCache`` instance associated with a file path derived from the identifier.
|
|
758
|
+
|
|
759
|
+
:param identifier: An optional string identifier for the tag cache. If provided, it must be in snake case format
|
|
760
|
+
and will be used to derive the file path for the tag cache. If not provided, a default file path will be used.
|
|
761
|
+
:return: A ``TagCache`` instance associated with the specified or default file path.
|
|
762
|
+
"""
|
|
763
|
+
if identifier is None:
|
|
764
|
+
return TagCache(file_path=tag_cache_file_path())
|
|
765
|
+
validate_snake_case(identifier)
|
|
766
|
+
return TagCache(file_path=tag_cache_file_path().parent / f"{identifier}.db")
|
|
@@ -55,10 +55,20 @@ class TagUtilsTest(unittest.TestCase):
|
|
|
55
55
|
dt_parse_iso("2021-01-01T00:00:00.000000+00:00") + datetime.timedelta(seconds=i + 1),
|
|
56
56
|
tags[i % len(tags)],
|
|
57
57
|
)
|
|
58
|
+
for i in range(1000):
|
|
59
|
+
tag_cache.add_bag_tag(
|
|
60
|
+
"dummy_vehicle",
|
|
61
|
+
f"20200101T000000-dummy_vehicle-{i // 100}.bag",
|
|
62
|
+
i % 100,
|
|
63
|
+
i % 100 + 1,
|
|
64
|
+
tags[i % len(tags)],
|
|
65
|
+
)
|
|
58
66
|
|
|
59
67
|
self.assertEqual(len(list(tag_cache.query())), 1000)
|
|
60
68
|
self.assertEqual(len(list(tag_cache.query("dummy_vehicle"))), 1000)
|
|
61
69
|
self.assertEqual(len(list(tag_cache.query(tagsets=[tagset]))), 500)
|
|
70
|
+
self.assertEqual(len(list(tag_cache.query(tagsets=[tagset], tagset_inverted=True))), 500)
|
|
71
|
+
self.assertEqual(len(list(tag_cache.query(tagsets=[tagset], tagset_inverted=False))), 500)
|
|
62
72
|
self.assertEqual(
|
|
63
73
|
len(list(tag_cache.query("dummy_vehicle",
|
|
64
74
|
dt_parse_iso("2020-01-01T00:00:00.000000+00:00"),
|
|
@@ -90,15 +100,36 @@ class TagUtilsTest(unittest.TestCase):
|
|
|
90
100
|
self.assertEqual(len(list(tag_cache.query("dummy_vehicle", tag_pattern="dummy:bar"))), 250)
|
|
91
101
|
self.assertEqual(len(list(tag_cache.query("dummy_vehicle", tag_pattern="dummy"))), 1000)
|
|
92
102
|
self.assertEqual(len(list(tag_cache.query("another_dummy_vehicle"))), 0)
|
|
93
|
-
|
|
94
|
-
self.assertEqual(len(list(tag_cache.
|
|
103
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag())), 1000)
|
|
104
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag(tagsets=[tagset]))), 500)
|
|
105
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag(tagsets=[tagset], tagset_inverted=True))), 500)
|
|
106
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag(tagsets=[tagset], tagset_inverted=False))), 500)
|
|
107
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag("dummy_vehicle"))), 1000)
|
|
108
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag("dummy_vehicle",
|
|
109
|
+
"20200101T000000-dummy_vehicle-0.bag"))),
|
|
110
|
+
100)
|
|
111
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag("dummy_vehicle",
|
|
112
|
+
"20200101T000000-dummy_vehicle-0.bag",
|
|
113
|
+
0,
|
|
114
|
+
50))),
|
|
115
|
+
51)
|
|
95
116
|
|
|
96
117
|
tag_cache.remove("dummy_vehicle",
|
|
97
118
|
dt_parse_iso("2020-01-01T00:00:00.000000+00:00"),
|
|
98
119
|
dt_parse_iso("2020-01-01T00:01:00.000000+00:00"))
|
|
120
|
+
tag_cache.remove_bag_tag("dummy_vehicle",
|
|
121
|
+
"20200101T000000-dummy_vehicle-0.bag")
|
|
99
122
|
|
|
100
123
|
self.assertEqual(len(list(tag_cache.query("dummy_vehicle"))), 939)
|
|
124
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag("dummy_vehicle"))), 900)
|
|
125
|
+
|
|
126
|
+
tag_cache.remove(tagsets=[tagset], tagset_inverted=True)
|
|
127
|
+
tag_cache.remove_bag_tag(tagsets=[tagset], tagset_inverted=True)
|
|
128
|
+
|
|
129
|
+
self.assertEqual(len(list(tag_cache.query())), 469)
|
|
130
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag())), 450)
|
|
101
131
|
|
|
102
132
|
tag_cache.clear()
|
|
103
133
|
|
|
104
|
-
self.assertEqual(len(list(tag_cache.query(
|
|
134
|
+
self.assertEqual(len(list(tag_cache.query())), 0)
|
|
135
|
+
self.assertEqual(len(list(tag_cache.query_bag_tag())), 0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/0-dummy
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/1-dummy
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/2-dummy
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/resources/unittest/pathutils/dummy.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMFile.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMNode.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMTags.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/OSMWay.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/carto/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/resources/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/__init__.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/apiutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/bagutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/config.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/datautils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/dockerutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/jsonutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/ormutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/pathutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/s3utils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/sqlutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/strutils.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/src/plexus/common/utils/testutils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/pose_test.py
RENAMED
|
File without changes
|
{plexus_python_common-1.0.49 → plexus_python_common-1.0.50}/test/plexus_tests/common/proj_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|