plexus-python-common 1.0.52__tar.gz → 1.0.53__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 (95) hide show
  1. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/PKG-INFO +1 -1
  2. plexus_python_common-1.0.53/src/plexus/common/utils/bagutils.py +329 -0
  3. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/tagutils.py +52 -62
  4. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  5. plexus_python_common-1.0.53/test/plexus_tests/common/utils/bagutils_test.py +82 -0
  6. plexus_python_common-1.0.53/test/plexus_tests/common/utils/tagutils_test.py +198 -0
  7. plexus_python_common-1.0.52/src/plexus/common/utils/bagutils.py +0 -214
  8. plexus_python_common-1.0.52/test/plexus_tests/common/utils/bagutils_test.py +0 -73
  9. plexus_python_common-1.0.52/test/plexus_tests/common/utils/tagutils_test.py +0 -210
  10. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/.editorconfig +0 -0
  11. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/.github/workflows/pr.yml +0 -0
  12. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/.github/workflows/push.yml +0 -0
  13. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/.gitignore +0 -0
  14. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/MANIFEST.in +0 -0
  15. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/README.md +0 -0
  16. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/VERSION +0 -0
  17. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/pyproject.toml +0 -0
  18. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  19. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  20. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  21. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/0-dummy +0 -0
  22. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/1-dummy +0 -0
  23. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/2-dummy +0 -0
  24. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  25. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  26. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  27. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  28. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  29. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  30. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  31. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  32. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  33. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  34. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  35. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  36. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/pathutils/dummy.txt +0 -0
  37. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  38. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  39. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  40. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  41. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  42. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  43. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  44. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  45. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  46. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  47. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  48. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  49. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/setup.cfg +0 -0
  50. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/setup.py +0 -0
  51. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/__init__.py +0 -0
  52. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/carto/OSMFile.py +0 -0
  53. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/carto/OSMNode.py +0 -0
  54. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/carto/OSMTags.py +0 -0
  55. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/carto/OSMWay.py +0 -0
  56. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/carto/__init__.py +0 -0
  57. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/pose.py +0 -0
  58. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/proj.py +0 -0
  59. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/resources/__init__.py +0 -0
  60. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/resources/tags/__init__.py +0 -0
  61. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/resources/tags/universal.tagset.yaml +0 -0
  62. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/__init__.py +0 -0
  63. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/apiutils.py +0 -0
  64. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/config.py +0 -0
  65. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/datautils.py +0 -0
  66. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/dockerutils.py +0 -0
  67. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/jsonutils.py +0 -0
  68. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/ormutils.py +0 -0
  69. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/pathutils.py +0 -0
  70. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/s3utils.py +0 -0
  71. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/sqlutils.py +0 -0
  72. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/strutils.py +0 -0
  73. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus/common/utils/testutils.py +0 -0
  74. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
  75. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  76. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  77. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/requires.txt +0 -0
  78. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  79. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/__init__.py +0 -0
  80. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/__init__.py +0 -0
  81. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/carto/__init__.py +0 -0
  82. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  83. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  84. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/pose_test.py +0 -0
  85. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/proj_test.py +0 -0
  86. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/__init__.py +0 -0
  87. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  88. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  89. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  90. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  91. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
  92. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  93. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  94. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/test/plexus_tests/common/utils/testutils_test.py +0 -0
  95. {plexus_python_common-1.0.52 → plexus_python_common-1.0.53}/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.52
3
+ Version: 1.0.53
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,329 @@
1
+ import contextlib
2
+ import os
3
+ from collections.abc import Generator
4
+ from contextlib import AbstractContextManager
5
+ from typing import Literal
6
+
7
+ import sqlalchemy as sa
8
+ import sqlalchemy.orm as sa_orm
9
+ from iker.common.utils.pathutils import make_path
10
+
11
+ __all__ = [
12
+ "SerializationFormat",
13
+ "BagSchema",
14
+ "BagMetadata",
15
+ "BagMessageDefinition",
16
+ "BagTopic",
17
+ "BagMessage",
18
+ "BagReader",
19
+ "BagWriter",
20
+ "bag_reader",
21
+ "bag_writer",
22
+ ]
23
+
24
+ SerializationFormat = Literal["cdr", "cdr2", "json", "yaml"]
25
+
26
+ default_bag_db_file = "bag.db"
27
+
28
+
29
+ class BagBase(sa_orm.DeclarativeBase):
30
+ pass
31
+
32
+
33
+ class BagSchema(BagBase):
34
+ __tablename__ = "schema"
35
+
36
+ schema_version: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, primary_key=True)
37
+ ros_distro: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
38
+
39
+
40
+ class BagMetadata(BagBase):
41
+ __tablename__ = "metadata"
42
+
43
+ id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, primary_key=True, autoincrement=True)
44
+ metadata_version: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, nullable=False, unique=True)
45
+ metadata_text: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
46
+
47
+
48
+ class BagMessageDefinition(BagBase):
49
+ __tablename__ = "message_definitions"
50
+
51
+ id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, primary_key=True, autoincrement=True)
52
+ message_type: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False, unique=True)
53
+ encoding: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
54
+ encoded_message_definition: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
55
+ type_description_hash: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
56
+
57
+ topics: sa_orm.Mapped[list["BagTopic"]] = sa_orm.relationship("BagTopic", back_populates="message_definition")
58
+
59
+
60
+ class BagTopic(BagBase):
61
+ __tablename__ = "topics"
62
+
63
+ id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, primary_key=True, autoincrement=True)
64
+ name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False, unique=True)
65
+ message_definition_id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer,
66
+ sa.ForeignKey("message_definitions.id"),
67
+ nullable=False)
68
+ serialization_format: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
69
+ type_description_hash: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.String, nullable=False)
70
+
71
+ messages: sa_orm.Mapped[list["BagMessage"]] = sa_orm.relationship("BagMessage", back_populates="topic")
72
+ message_definition: sa_orm.Mapped["BagMessageDefinition"] = sa_orm.relationship("BagMessageDefinition",
73
+ back_populates="topics")
74
+
75
+
76
+ class BagMessage(BagBase):
77
+ __tablename__ = "messages"
78
+
79
+ id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, primary_key=True, autoincrement=True)
80
+ topic_id: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, sa.ForeignKey("topics.id"), nullable=False)
81
+ timestamp: sa_orm.Mapped[int] = sa_orm.mapped_column(sa.Integer, nullable=False)
82
+ data: sa_orm.Mapped[bytes] = sa_orm.mapped_column(sa.LargeBinary, nullable=False)
83
+
84
+ topic: sa_orm.Mapped["BagTopic"] = sa_orm.relationship("BagTopic", back_populates="messages")
85
+
86
+
87
+ class BagIOBase(object):
88
+ def __init__(self, bag_file_path: str | os.PathLike[str]):
89
+ self.bag_file_path = make_path(bag_file_path)
90
+
91
+ self.engine: sa.Engine | None = None
92
+ self.session: sa_orm.Session | None = None
93
+
94
+ self.message_definitions_lookup: dict[str, int] = {}
95
+ self.topics_lookup: dict[str, int] = {}
96
+
97
+ @contextlib.contextmanager
98
+ def setup(self):
99
+ self.engine = sa.create_engine(f"sqlite:///{str(self.bag_file_path.absolute())}")
100
+ self.session = sa_orm.Session(self.engine)
101
+ try:
102
+ yield
103
+ finally:
104
+ self.message_definitions_lookup = {db_message_definition.message_type: db_message_definition.id
105
+ for db_message_definition in self.message_definitions()}
106
+ self.topics_lookup = {db_topic.name: db_topic.id for db_topic in self.topics()}
107
+
108
+ @contextlib.contextmanager
109
+ def teardown(self):
110
+ try:
111
+ yield
112
+ finally:
113
+ self.session.close()
114
+ self.engine.dispose()
115
+
116
+ def get_metadata(self, metadata_version: int) -> BagMetadata | None:
117
+ return (
118
+ self
119
+ .session
120
+ .query(BagMetadata)
121
+ .filter(BagMetadata.metadata_version == metadata_version)
122
+ .one_or_none()
123
+ )
124
+
125
+ def get_message_definition(self, message_type: str) -> BagMessageDefinition | None:
126
+ return (
127
+ self
128
+ .session
129
+ .query(BagMessageDefinition)
130
+ .filter(BagMessageDefinition.message_type == message_type)
131
+ .one_or_none()
132
+ )
133
+
134
+ def get_topic(self, name: str) -> BagTopic | None:
135
+ return self.session.query(BagTopic).filter(BagTopic.name == name).one_or_none()
136
+
137
+ def metadata(self) -> list[BagMetadata]:
138
+ return self.session.query(BagMetadata).all()
139
+
140
+ def message_definitions(self) -> list[BagMessageDefinition]:
141
+ return self.session.query(BagMessageDefinition).all()
142
+
143
+ def topics(self) -> list[BagTopic]:
144
+ return self.session.query(BagTopic).all()
145
+
146
+
147
+ class BagReader(AbstractContextManager, BagIOBase):
148
+ @staticmethod
149
+ def open_ros2(db_dir: str | os.PathLike[str], db_filename: str = default_bag_db_file, **kwargs) -> "BagReader":
150
+ """
151
+ Creates a BagReader instance to read messages from a ROS2 bag file in the specified directory.
152
+
153
+ :param db_dir: path to the directory where the SQLite DB file is located.
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.
157
+ """
158
+ return BagReader(make_path(db_dir) / db_filename)
159
+
160
+ def __init__(self, bag_file_path: str | os.PathLike[str]):
161
+ """
162
+ Creates a BagReader instance to read messages from a ROS2 bag file.
163
+
164
+ :param bag_file_path: path to the SQLite DB file to read.
165
+ """
166
+ super().__init__(bag_file_path)
167
+
168
+ if not self.bag_file_path.exists():
169
+ raise FileNotFoundError(f"could not find SQLite DB at '{str(self.bag_file_path)}'")
170
+
171
+ def __enter__(self):
172
+ with self.setup():
173
+ pass
174
+ return self
175
+
176
+ def __exit__(self, exc_type, exc_val, exc_tb):
177
+ with self.teardown():
178
+ pass
179
+
180
+ def iter_messages(
181
+ self,
182
+ topic_names: list[str] | None = None,
183
+ begin_timestamp: int | None = None,
184
+ end_timestamp: int | None = None,
185
+ *,
186
+ batch_size: int = 1000,
187
+ ) -> Generator[BagMessage, None, None]:
188
+ query = self.session.query(BagMessage)
189
+ if topic_names is not None:
190
+ topic_ids = [topic_id for topic_name, topic_id in self.topics_lookup.items() if topic_name in topic_names]
191
+ query = query.where(BagMessage.topic_id.in_(topic_ids))
192
+ if begin_timestamp is not None:
193
+ query = query.where(BagMessage.timestamp >= begin_timestamp)
194
+ if end_timestamp is not None:
195
+ query = query.where(BagMessage.timestamp <= end_timestamp)
196
+
197
+ for db_message in query.order_by(BagMessage.timestamp.asc()).yield_per(batch_size):
198
+ yield db_message
199
+
200
+
201
+ class BagWriter(AbstractContextManager, BagIOBase):
202
+ @staticmethod
203
+ def open_ros2(db_dir: str | os.PathLike[str], db_filename: str = default_bag_db_file, **kwargs) -> "BagWriter":
204
+ """
205
+ Creates a BagWriter instance to write messages to a ROS2 bag file in the specified directory.
206
+
207
+ :param db_dir: path to the directory where the SQLite DB file will be created.
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.
211
+ """
212
+ return BagWriter(make_path(db_dir) / db_filename, **kwargs)
213
+
214
+ def __init__(self, bag_file_path: str | os.PathLike[str], *, overwrite: bool = True, exist_ok: bool = False):
215
+ """
216
+ Creates a BagWriter instance to write messages to a ROS2 bag file.
217
+
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.
223
+ """
224
+ super().__init__(bag_file_path)
225
+
226
+ if not self.bag_file_path.parent.exists():
227
+ self.bag_file_path.parent.mkdir(parents=True, exist_ok=True)
228
+ if self.bag_file_path.exists():
229
+ if not overwrite and not exist_ok:
230
+ raise FileExistsError(f"SQLite DB file already exists at '{str(self.bag_file_path)}'")
231
+ if overwrite:
232
+ self.bag_file_path.unlink()
233
+
234
+ def __enter__(self):
235
+ with self.setup():
236
+ BagBase.metadata.create_all(self.engine)
237
+ return self
238
+
239
+ def __exit__(self, exc_type, exc_val, exc_tb):
240
+ with self.teardown():
241
+ if exc_type is None:
242
+ self.session.commit()
243
+ else:
244
+ self.session.rollback()
245
+
246
+ def add_metadata(self, metadata_version: int, metadata_text: str, *, return_existing: bool = False) -> BagMetadata:
247
+ if (db_metadata := self.get_metadata(metadata_version)) is not None:
248
+ if return_existing:
249
+ return db_metadata
250
+ raise ValueError(f"metadata with metadata_version '{metadata_version}' already exists in the bag file")
251
+
252
+ db_metadata = BagMetadata(metadata_version=metadata_version, metadata_text=metadata_text)
253
+ self.session.add(db_metadata)
254
+ self.session.flush()
255
+
256
+ return db_metadata
257
+
258
+ def add_message_definition(
259
+ self,
260
+ message_type: str,
261
+ encoding: str = "",
262
+ encoded_message_definition: str = "",
263
+ type_description_hash: str = "",
264
+ *,
265
+ return_existing: bool = False,
266
+ ) -> BagMessageDefinition:
267
+ if (db_message_definition := self.get_message_definition(message_type)) is not None:
268
+ if return_existing:
269
+ return db_message_definition
270
+ raise ValueError(f"message definition with message_type '{message_type}' already exists in the bag file")
271
+
272
+ db_message_definition = BagMessageDefinition(
273
+ message_type=message_type,
274
+ encoding=encoding,
275
+ encoded_message_definition=encoded_message_definition,
276
+ type_description_hash=type_description_hash,
277
+ )
278
+ self.session.add(db_message_definition)
279
+ self.session.flush()
280
+
281
+ self.message_definitions_lookup[db_message_definition.message_type] = db_message_definition.id
282
+
283
+ return db_message_definition
284
+
285
+ def add_topic(
286
+ self,
287
+ name: str,
288
+ message_type: str,
289
+ serialization_format: SerializationFormat = "cdr",
290
+ type_description_hash: str = "",
291
+ *,
292
+ return_existing: bool = False,
293
+ ) -> BagTopic:
294
+ message_definition_id = self.message_definitions_lookup.get(message_type)
295
+ if message_definition_id is None:
296
+ raise ValueError(f"message definition with message_type '{message_type}' does not exist in the bag file")
297
+
298
+ if (db_topic := self.get_topic(name)) is not None:
299
+ if return_existing:
300
+ return db_topic
301
+ raise ValueError(f"topic with name '{name}' already exists in the bag file")
302
+
303
+ db_topic = BagTopic(
304
+ name=name,
305
+ message_definition_id=message_definition_id,
306
+ serialization_format=serialization_format,
307
+ type_description_hash=type_description_hash,
308
+ )
309
+ self.session.add(db_topic)
310
+ self.session.flush()
311
+
312
+ self.topics_lookup[db_topic.name] = db_topic.id
313
+
314
+ return db_topic
315
+
316
+ def write_message(self, name: str, timestamp: int, data: bytes) -> BagMessage:
317
+ topic_id = self.topics_lookup.get(name)
318
+ if topic_id is None:
319
+ raise ValueError(f"topic with name '{name}' does not exist in the bag file")
320
+
321
+ db_message = BagMessage(topic_id=topic_id, timestamp=timestamp, data=data)
322
+ self.session.add(db_message)
323
+ self.session.flush()
324
+
325
+ return db_message
326
+
327
+
328
+ bag_reader = BagReader
329
+ bag_writer = BagWriter