plexus-python-common 1.0.46__tar.gz → 1.0.48__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.46 → plexus_python_common-1.0.48}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/bagutils.py +33 -34
  3. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/config.py +5 -3
  4. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/jsonutils.py +1 -1
  5. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/ormutils.py +9 -9
  6. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/s3utils.py +31 -26
  7. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/tagutils.py +10 -10
  8. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  9. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/SOURCES.txt +22 -19
  10. plexus_python_common-1.0.48/test/plexus_tests/__init__.py +0 -0
  11. plexus_python_common-1.0.48/test/plexus_tests/common/__init__.py +0 -0
  12. plexus_python_common-1.0.48/test/plexus_tests/common/carto/__init__.py +0 -0
  13. plexus_python_common-1.0.48/test/plexus_tests/common/utils/__init__.py +0 -0
  14. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/jsonutils_test.py +2 -3
  15. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/ormutils_test.py +83 -0
  16. plexus_python_common-1.0.46/test/plexus_tests/common/utils/shutils_test.py → plexus_python_common-1.0.48/test/plexus_tests/common/utils/pathutils_test.py +9 -14
  17. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/s3utils_test.py +69 -59
  18. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/tagutils_test.py +1 -2
  19. plexus_python_common-1.0.48/test/testenv.py +15 -0
  20. plexus_python_common-1.0.46/test/plexus_test.py +0 -13
  21. plexus_python_common-1.0.46/test/plexus_tests/__init__.py +0 -15
  22. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/.editorconfig +0 -0
  23. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/.github/workflows/pr.yml +0 -0
  24. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/.github/workflows/push.yml +0 -0
  25. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/.gitignore +0 -0
  26. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/MANIFEST.in +0 -0
  27. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/README.md +0 -0
  28. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/VERSION +0 -0
  29. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/pyproject.toml +0 -0
  30. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  31. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  32. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  33. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/0-dummy +0 -0
  34. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/1-dummy +0 -0
  35. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/2-dummy +0 -0
  36. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.0.0.jsonl +0 -0
  37. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.0.0.vol-0.jsonl +0 -0
  38. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.0.jsonl +0 -0
  39. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.1.1.jsonl +0 -0
  40. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.1.1.vol-1.jsonl +0 -0
  41. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.1.jsonl +0 -0
  42. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.2.2.jsonl +0 -0
  43. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.2.2.vol-2.jsonl +0 -0
  44. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.2.jsonl +0 -0
  45. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.csv.part0 +0 -0
  46. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.csv.part1 +0 -0
  47. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.csv.part2 +0 -0
  48. {plexus_python_common-1.0.46/resources/unittest/shutils → plexus_python_common-1.0.48/resources/unittest/pathutils}/dummy.txt +0 -0
  49. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  50. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  51. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  52. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  53. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  54. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  55. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  56. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  57. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  58. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  59. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  60. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  61. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/setup.cfg +0 -0
  62. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/setup.py +0 -0
  63. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/__init__.py +0 -0
  64. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/carto/OSMFile.py +0 -0
  65. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/carto/OSMNode.py +0 -0
  66. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/carto/OSMTags.py +0 -0
  67. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/carto/OSMWay.py +0 -0
  68. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/carto/__init__.py +0 -0
  69. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/pose.py +0 -0
  70. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/proj.py +0 -0
  71. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/resources/__init__.py +0 -0
  72. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/resources/tags/__init__.py +0 -0
  73. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/resources/tags/universal.tagset.yaml +0 -0
  74. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/__init__.py +0 -0
  75. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/apiutils.py +0 -0
  76. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/datautils.py +0 -0
  77. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/dockerutils.py +0 -0
  78. /plexus_python_common-1.0.46/src/plexus/common/utils/shutils.py → /plexus_python_common-1.0.48/src/plexus/common/utils/pathutils.py +0 -0
  79. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/sqlutils.py +0 -0
  80. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/strutils.py +0 -0
  81. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus/common/utils/testutils.py +0 -0
  82. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  83. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  84. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/requires.txt +0 -0
  85. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  86. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  87. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  88. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/pose_test.py +0 -0
  89. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/proj_test.py +0 -0
  90. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  91. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  92. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  93. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  94. {plexus_python_common-1.0.46 → plexus_python_common-1.0.48}/test/plexus_tests/common/utils/testutils_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.46
3
+ Version: 1.0.48
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -5,6 +5,7 @@ from typing import Literal
5
5
 
6
6
  import sqlalchemy as sa
7
7
  import sqlalchemy.orm as sa_orm
8
+ from iker.common.utils.pathutils import make_path
8
9
 
9
10
  __all__ = [
10
11
  "SerializationFormat",
@@ -77,12 +78,20 @@ class BagMessage(BagBase):
77
78
 
78
79
 
79
80
  class BagReader(AbstractContextManager):
80
- def __init__(self, db_dir: str, db_file: str = default_bag_db_file):
81
- self.db_path = os.path.join(db_dir, db_file)
82
- if not os.path.exists(self.db_path):
83
- raise FileNotFoundError(f"could not find SQLite DB at '{db_dir}'")
84
-
85
- self.engine = sa.create_engine(f"sqlite:///{self.db_path}")
81
+ def __init__(self, db_dir: str | os.PathLike[str], db_file: str | os.PathLike[str] = default_bag_db_file):
82
+ """
83
+ Creates a BagReader instance to read messages from a ROS2 bag file.
84
+
85
+ :param db_dir: directory containing the SQLite DB file.
86
+ :param db_file: name of the SQLite DB file.
87
+ :return: BagReader instance.
88
+ """
89
+ self.db_dir = make_path(db_dir)
90
+ self.db_path = self.db_dir / db_file
91
+ if not self.db_path.exists():
92
+ raise FileNotFoundError(f"could not find SQLite DB at '{str(self.db_path)}'")
93
+
94
+ self.engine = sa.create_engine(f"sqlite:///{str(self.db_path.absolute())}")
86
95
  self.session = sa_orm.Session(self.engine)
87
96
  self.topic_map = None
88
97
 
@@ -121,12 +130,22 @@ class BagReader(AbstractContextManager):
121
130
 
122
131
 
123
132
  class BagWriter(AbstractContextManager):
124
- def __init__(self, db_dir: str, db_file: str = default_bag_db_file):
125
- self.db_path = os.path.join(db_dir, db_file)
126
- if os.path.exists(self.db_path):
127
- os.remove(self.db_path)
128
-
129
- self.engine = sa.create_engine(f"sqlite:///{self.db_path}")
133
+ def __init__(self, db_dir: str | os.PathLike[str], db_file: str | os.PathLike[str] = default_bag_db_file):
134
+ """
135
+ Creates a BagWriter instance to write messages to a ROS2 bag file.
136
+
137
+ :param db_dir: directory to store the SQLite DB file.
138
+ :param db_file: name of the SQLite DB file.
139
+ :return: BagWriter instance.
140
+ """
141
+ self.db_dir = make_path(db_dir)
142
+ if not self.db_dir.exists():
143
+ self.db_dir.mkdir(parents=True, exist_ok=True)
144
+ self.db_path = self.db_dir / db_file
145
+ if self.db_path.exists():
146
+ self.db_path.unlink()
147
+
148
+ self.engine = sa.create_engine(f"sqlite:///{str(self.db_path.absolute())}")
130
149
  self.session = sa_orm.Session(self.engine)
131
150
  self.topic_map = {}
132
151
 
@@ -191,25 +210,5 @@ class BagWriter(AbstractContextManager):
191
210
  return db_message
192
211
 
193
212
 
194
- def bag_reader(db_dir: str, db_file: str = default_bag_db_file) -> BagReader:
195
- """
196
- Creates a BagReader instance to read messages from a ROS2 bag file.
197
-
198
- :param db_dir: directory containing the SQLite DB file.
199
- :param db_file: name of the SQLite DB file.
200
- :return: BagReader instance.
201
- """
202
- return BagReader(db_dir, db_file)
203
-
204
-
205
- def bag_writer(db_dir: str, db_file: str = default_bag_db_file) -> BagWriter:
206
- """
207
- Creates a BagWriter instance to write messages to a ROS2 bag file.
208
-
209
- :param db_dir: directory to store the SQLite DB file.
210
- :param db_file: name of the SQLite DB file.
211
- :return: BagWriter instance.
212
- """
213
- if not os.path.exists(db_dir):
214
- os.makedirs(db_dir, exist_ok=True)
215
- return BagWriter(db_dir, db_file)
213
+ bag_reader = BagReader
214
+ bag_writer = BagWriter
@@ -1,17 +1,19 @@
1
+ import os
2
+
1
3
  from iker.common.utils import logger
2
4
  from iker.common.utils.config import Config
3
5
  from iker.common.utils.funcutils import memorized
4
- from iker.common.utils.shutils import expanded_path
6
+ from iker.common.utils.pathutils import make_path
5
7
 
6
8
 
7
9
  @memorized()
8
- def config(config_path: str = "") -> Config:
10
+ def config(config_path: str | os.PathLike[str] = "") -> Config:
9
11
  default_items: list[tuple[str, str, str]] = [
10
12
  ("plexus", "logging.level", "INFO"),
11
13
  ("plexus", "logging.format", "%(asctime)s [%(levelname)s] %(name)s: %(message)s"),
12
14
  ]
13
15
 
14
- instance = Config(config_path or expanded_path("~/.plexus.cfg"))
16
+ instance = Config(config_path or make_path("~/.plexus.cfg", expand=True, normalize=True, absolute=True))
15
17
  instance.restore()
16
18
  instance.update(default_items, overwrite=False)
17
19
 
@@ -9,7 +9,7 @@ from iker.common.utils.iterutils import batched
9
9
  from iker.common.utils.jsonutils import JsonType, JsonValueCompatible
10
10
  from iker.common.utils.jsonutils import json_reformat
11
11
 
12
- from plexus.common.utils.shutils import collect_volumed_filenames, populate_volumed_filenames
12
+ from plexus.common.utils.pathutils import collect_volumed_filenames, populate_volumed_filenames
13
13
 
14
14
  __all__ = [
15
15
  "json_datetime_decoder",
@@ -1146,7 +1146,7 @@ def db_read_active_snapshot_model_of_record[SnapshotModelT: SnapshotModelMixin](
1146
1146
  ) -> SnapshotModelT:
1147
1147
  db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1148
1148
  if db_instance is None:
1149
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1149
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1150
1150
 
1151
1151
  return db_instance
1152
1152
 
@@ -1218,7 +1218,7 @@ def db_update_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1218
1218
  ) -> SnapshotModelT:
1219
1219
  db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1220
1220
  if db_instance is None:
1221
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1221
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1222
1222
 
1223
1223
  db_instance.expired_at = updated_at
1224
1224
  db_instance = clone_snapshot_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
@@ -1247,7 +1247,7 @@ def db_expire_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1247
1247
  .one_or_none()
1248
1248
  )
1249
1249
  if db_instance is None:
1250
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1250
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1251
1251
 
1252
1252
  db_instance.expired_at = updated_at
1253
1253
  db_instance = clone_snapshot_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
@@ -1275,7 +1275,7 @@ def db_activate_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1275
1275
  .first()
1276
1276
  )
1277
1277
  if db_instance is None:
1278
- raise sa_exc.NoResultFound(f"Expired '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1278
+ raise sa_exc.NoResultFound(f"expired '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1279
1279
 
1280
1280
  db_new_instance = clone_snapshot_model_instance(model, db_instance)
1281
1281
  db_new_instance.record_sqn = record_sqn
@@ -1371,7 +1371,7 @@ def db_read_active_revision_model_of_record[RevisionModelT: RevisionModelMixin](
1371
1371
  ) -> RevisionModelT:
1372
1372
  db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1373
1373
  if db_instance is None:
1374
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1374
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1375
1375
 
1376
1376
  return db_instance
1377
1377
 
@@ -1443,7 +1443,7 @@ def db_update_revision_model[RevisionModelT: RevisionModelMixin](
1443
1443
  ) -> RevisionModelT:
1444
1444
  db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1445
1445
  if db_instance is None:
1446
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1446
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1447
1447
 
1448
1448
  db_instance.expired_at = updated_at
1449
1449
  db_instance = clone_revision_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
@@ -1474,7 +1474,7 @@ def db_expire_revision_model[RevisionModelT: RevisionModelMixin](
1474
1474
  .one_or_none()
1475
1475
  )
1476
1476
  if db_instance is None:
1477
- raise sa_exc.NoResultFound(f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1477
+ raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1478
1478
 
1479
1479
  db_instance.expired_at = updated_at
1480
1480
  db_instance = clone_revision_model_instance(model, db_instance, clear_meta_fields=False, inplace=True)
@@ -1492,7 +1492,7 @@ def db_activate_revision_model[RevisionModelT: RevisionModelMixin](
1492
1492
  db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1493
1493
  if db_instance is not None:
1494
1494
  raise sa_exc.MultipleResultsFound(
1495
- f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' already exists")
1495
+ f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' already exists")
1496
1496
 
1497
1497
  db_instance = (
1498
1498
  db
@@ -1502,7 +1502,7 @@ def db_activate_revision_model[RevisionModelT: RevisionModelMixin](
1502
1502
  .first()
1503
1503
  )
1504
1504
  if db_instance is None:
1505
- raise sa_exc.NoResultFound(f"Expired '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1505
+ raise sa_exc.NoResultFound(f"expired '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1506
1506
 
1507
1507
  db_new_instance = clone_revision_model_instance(model, db_instance)
1508
1508
  db_new_instance.record_sqn = record_sqn
@@ -6,7 +6,7 @@ import functools
6
6
  import io
7
7
  import mimetypes
8
8
  import os
9
- import os.path
9
+ import pathlib
10
10
  import shutil
11
11
  import tempfile
12
12
  import typing
@@ -20,7 +20,7 @@ import fsspec
20
20
  import fsspec.utils
21
21
  from iker.common.utils.iterutils import chunk_between, head, last
22
22
  from iker.common.utils.jsonutils import JsonObject
23
- from iker.common.utils.shutils import glob_match, listfile, path_depth
23
+ from iker.common.utils.pathutils import glob_match, make_path, path_depth, scan_files
24
24
  from iker.common.utils.strutils import is_empty, trim_to_none
25
25
  from mypy_boto3_s3 import S3Client
26
26
  from rich.progress import BarColumn, DownloadColumn, Progress, TextColumn, TransferSpeedColumn
@@ -30,7 +30,7 @@ __all__ = [
30
30
  "s3_make_client",
31
31
  "s3_head_object",
32
32
  "s3_list_objects",
33
- "s3_listfile",
33
+ "s3_list_files",
34
34
  "s3_cp_download",
35
35
  "s3_cp_upload",
36
36
  "s3_sync_download",
@@ -40,7 +40,7 @@ __all__ = [
40
40
  "s3_make_progressed_client",
41
41
  "ArchiveMemberChunk",
42
42
  "s3_archive_member_tree",
43
- "s3_archive_listfile",
43
+ "s3_archive_list_files",
44
44
  "s3_archive_open_member",
45
45
  "s3_archive_use_ranged_requests",
46
46
  "s3_archive_use_chunked_reads",
@@ -147,7 +147,7 @@ def s3_list_objects(
147
147
  continuation_token = response.get("NextContinuationToken")
148
148
 
149
149
 
150
- def s3_listfile(
150
+ def s3_list_files(
151
151
  client: S3Client,
152
152
  bucket: str,
153
153
  prefix: str,
@@ -173,9 +173,10 @@ def s3_listfile(
173
173
  prefix = prefix + "/"
174
174
 
175
175
  def filter_object_meta(object_meta: S3ObjectMeta) -> bool:
176
- if 0 < depth <= path_depth(prefix, os.path.dirname(object_meta.key)):
176
+ object_path = make_path(object_meta.key)
177
+ if 0 < depth <= path_depth(prefix, object_path.parent):
177
178
  return False
178
- if len(glob_match([os.path.basename(object_meta.key)], include_patterns, exclude_patterns)) == 0:
179
+ if len(glob_match([object_path.name], include_patterns, exclude_patterns)) == 0:
179
180
  return False
180
181
  return True
181
182
 
@@ -238,16 +239,18 @@ def s3_sync_download(
238
239
  if not prefix.endswith("/"):
239
240
  prefix = prefix + "/"
240
241
 
241
- objects = s3_listfile(client,
242
- bucket,
243
- prefix,
244
- include_patterns=include_patterns,
245
- exclude_patterns=exclude_patterns,
246
- depth=depth)
242
+ dir_path = make_path(dir_path)
243
+
244
+ objects = s3_list_files(client,
245
+ bucket,
246
+ prefix,
247
+ include_patterns=include_patterns,
248
+ exclude_patterns=exclude_patterns,
249
+ depth=depth)
247
250
 
248
251
  def download_file(key: str):
249
- file_path = os.path.join(dir_path, key[len(prefix):])
250
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
252
+ file_path = dir_path / key[len(prefix):]
253
+ file_path.parent.mkdir(parents=True, exist_ok=True)
251
254
  s3_cp_download(client, bucket, key, file_path)
252
255
 
253
256
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
@@ -293,13 +296,15 @@ def s3_sync_upload(
293
296
  if not prefix.endswith("/"):
294
297
  prefix = prefix + "/"
295
298
 
296
- file_paths = listfile(dir_path,
297
- include_patterns=include_patterns,
298
- exclude_patterns=exclude_patterns,
299
- depth=depth)
299
+ dir_path = make_path(dir_path)
300
+
301
+ file_paths = scan_files(dir_path,
302
+ include_patterns=include_patterns,
303
+ exclude_patterns=exclude_patterns,
304
+ depth=depth)
300
305
 
301
- def upload_file(file_path: str):
302
- s3_cp_upload(client, file_path, bucket, prefix + os.path.relpath(file_path, dir_path))
306
+ def upload_file(file_path: pathlib.Path):
307
+ s3_cp_upload(client, file_path, bucket, prefix + str(file_path.relative_to(dir_path)))
303
308
 
304
309
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
305
310
  futures = [executor.submit(upload_file, file_path) for file_path in file_paths]
@@ -415,7 +420,7 @@ class S3ClientProgressProxy(object):
415
420
  Callback=None,
416
421
  Config=None,
417
422
  ):
418
- bytes_total = os.path.getsize(Filename)
423
+ bytes_total = make_path(Filename).stat().st_size
419
424
  with (
420
425
  contextlib.nullcontext(Callback) if Callback is not None
421
426
  else self.make_transfer_callback(Key, bytes_total, "upload")
@@ -585,7 +590,7 @@ def s3_archive_member_tree(
585
590
  return root_member_tree
586
591
 
587
592
 
588
- def s3_archive_listfile(
593
+ def s3_archive_list_files(
589
594
  client: S3Client,
590
595
  bucket: str,
591
596
  key: str,
@@ -597,7 +602,7 @@ def s3_archive_listfile(
597
602
  under that directory will be included in the results.
598
603
 
599
604
  Example usage:
600
- >>> archive_size, member_zip_infos, missed_members = s3_archive_listfile(client, bucket, key, members=["file1.txt", "dir1/"])
605
+ >>> archive_size, member_zip_infos, missed_members = s3_archive_list_files(client, bucket, key, members=["file1.txt", "dir1/"])
601
606
  >>> for info in member_zip_infos:
602
607
  ... print(info.filename, info.file_size)
603
608
  >>> if missed_members:
@@ -836,9 +841,9 @@ def s3_archive_open_members(
836
841
 
837
842
  s3_options = s3_options_from_s3_client(client)
838
843
 
839
- archive_size, member_zip_infos, missed_members = s3_archive_listfile(client, bucket, key, members)
844
+ archive_size, member_zip_infos, missed_members = s3_archive_list_files(client, bucket, key, members)
840
845
  if missed_members:
841
- raise FileNotFoundError(f"Archive members not found: {', '.join(missed_members)}")
846
+ raise FileNotFoundError(f"archive members not found: {', '.join(missed_members)}")
842
847
 
843
848
  if callable(use_ranged_requests):
844
849
  use_ranged_requests = use_ranged_requests(archive_size, member_zip_infos)
@@ -1,6 +1,7 @@
1
1
  import dataclasses
2
2
  import datetime
3
- import os.path
3
+ import os
4
+ import pathlib
4
5
  import textwrap
5
6
  import typing
6
7
  from collections.abc import Generator, Mapping, Sequence
@@ -324,18 +325,17 @@ def render_tagset_markdown_readme(tagset: Tagset) -> str:
324
325
 
325
326
 
326
327
  @singleton
327
- def tag_cache_file_path() -> str:
328
- return os.path.expanduser(f"~/.local/plus/datahub/tag_cache/{randomizer().random_alphanumeric(7)}.db")
328
+ def tag_cache_file_path() -> pathlib.Path:
329
+ return pathlib.Path.home() / ".local" / "plus" / "datahub" / "tag_cache" / f"{randomizer().random_alphanumeric(7)}.db"
329
330
 
330
331
 
331
332
  class TagCache(object):
332
- def __init__(self, *, file_path: str | None = None, fail_if_exists: bool = False):
333
- self.file_path = file_path or tag_cache_file_path()
334
- if fail_if_exists and os.path.exists(self.file_path):
335
- raise FileExistsError(f"tag cache file '{self.file_path}' already exists")
336
- if not os.path.exists(os.path.dirname(self.file_path)):
337
- os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
338
- self.conn_maker = ConnectionMaker.from_url(f"sqlite:///{self.file_path}",
333
+ def __init__(self, *, file_path: str | os.PathLike[str] | None = None, fail_if_exists: bool = False):
334
+ self.file_path = pathlib.Path(file_path or tag_cache_file_path())
335
+ if fail_if_exists and self.file_path.exists():
336
+ raise FileExistsError(f"tag cache file '{str(self.file_path)}' already exists")
337
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
338
+ self.conn_maker = ConnectionMaker.from_url(f"sqlite:///{str(self.file_path.absolute())}",
339
339
  engine_opts=dict(connect_args={"check_same_thread": False}))
340
340
  BaseModel.metadata.create_all(self.conn_maker.engine)
341
341
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.46
3
+ Version: 1.0.48
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -10,6 +10,22 @@ setup.py
10
10
  resources/unittest/jsonutils/dummy.0.jsonl
11
11
  resources/unittest/jsonutils/dummy.1.jsonl
12
12
  resources/unittest/jsonutils/dummy.2.jsonl
13
+ resources/unittest/pathutils/0-dummy
14
+ resources/unittest/pathutils/1-dummy
15
+ resources/unittest/pathutils/2-dummy
16
+ resources/unittest/pathutils/dummy.0.0.jsonl
17
+ resources/unittest/pathutils/dummy.0.0.vol-0.jsonl
18
+ resources/unittest/pathutils/dummy.0.jsonl
19
+ resources/unittest/pathutils/dummy.1.1.jsonl
20
+ resources/unittest/pathutils/dummy.1.1.vol-1.jsonl
21
+ resources/unittest/pathutils/dummy.1.jsonl
22
+ resources/unittest/pathutils/dummy.2.2.jsonl
23
+ resources/unittest/pathutils/dummy.2.2.vol-2.jsonl
24
+ resources/unittest/pathutils/dummy.2.jsonl
25
+ resources/unittest/pathutils/dummy.csv.part0
26
+ resources/unittest/pathutils/dummy.csv.part1
27
+ resources/unittest/pathutils/dummy.csv.part2
28
+ resources/unittest/pathutils/dummy.txt
13
29
  resources/unittest/s3utils/dir.baz/file.bar.baz
14
30
  resources/unittest/s3utils/dir.baz/file.foo.bar
15
31
  resources/unittest/s3utils/dir.baz/file.foo.baz
@@ -22,22 +38,6 @@ resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz
22
38
  resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz
23
39
  resources/unittest/s3utils_archive/archive.compressed.zip
24
40
  resources/unittest/s3utils_archive/archive.uncompressed.zip
25
- resources/unittest/shutils/0-dummy
26
- resources/unittest/shutils/1-dummy
27
- resources/unittest/shutils/2-dummy
28
- resources/unittest/shutils/dummy.0.0.jsonl
29
- resources/unittest/shutils/dummy.0.0.vol-0.jsonl
30
- resources/unittest/shutils/dummy.0.jsonl
31
- resources/unittest/shutils/dummy.1.1.jsonl
32
- resources/unittest/shutils/dummy.1.1.vol-1.jsonl
33
- resources/unittest/shutils/dummy.1.jsonl
34
- resources/unittest/shutils/dummy.2.2.jsonl
35
- resources/unittest/shutils/dummy.2.2.vol-2.jsonl
36
- resources/unittest/shutils/dummy.2.jsonl
37
- resources/unittest/shutils/dummy.csv.part0
38
- resources/unittest/shutils/dummy.csv.part1
39
- resources/unittest/shutils/dummy.csv.part2
40
- resources/unittest/shutils/dummy.txt
41
41
  src/plexus/common/__init__.py
42
42
  src/plexus/common/pose.py
43
43
  src/plexus/common/proj.py
@@ -57,8 +57,8 @@ src/plexus/common/utils/datautils.py
57
57
  src/plexus/common/utils/dockerutils.py
58
58
  src/plexus/common/utils/jsonutils.py
59
59
  src/plexus/common/utils/ormutils.py
60
+ src/plexus/common/utils/pathutils.py
60
61
  src/plexus/common/utils/s3utils.py
61
- src/plexus/common/utils/shutils.py
62
62
  src/plexus/common/utils/sqlutils.py
63
63
  src/plexus/common/utils/strutils.py
64
64
  src/plexus/common/utils/tagutils.py
@@ -69,19 +69,22 @@ src/plexus_python_common.egg-info/dependency_links.txt
69
69
  src/plexus_python_common.egg-info/not-zip-safe
70
70
  src/plexus_python_common.egg-info/requires.txt
71
71
  src/plexus_python_common.egg-info/top_level.txt
72
- test/plexus_test.py
72
+ test/testenv.py
73
73
  test/plexus_tests/__init__.py
74
+ test/plexus_tests/common/__init__.py
74
75
  test/plexus_tests/common/pose_test.py
75
76
  test/plexus_tests/common/proj_test.py
77
+ test/plexus_tests/common/carto/__init__.py
76
78
  test/plexus_tests/common/carto/osm_file_test.py
77
79
  test/plexus_tests/common/carto/osm_tags_test.py
80
+ test/plexus_tests/common/utils/__init__.py
78
81
  test/plexus_tests/common/utils/bagutils_test.py
79
82
  test/plexus_tests/common/utils/datautils_test.py
80
83
  test/plexus_tests/common/utils/dockerutils_test.py
81
84
  test/plexus_tests/common/utils/jsonutils_test.py
82
85
  test/plexus_tests/common/utils/ormutils_test.py
86
+ test/plexus_tests/common/utils/pathutils_test.py
83
87
  test/plexus_tests/common/utils/s3utils_test.py
84
- test/plexus_tests/common/utils/shutils_test.py
85
88
  test/plexus_tests/common/utils/strutils_test.py
86
89
  test/plexus_tests/common/utils/tagutils_test.py
87
90
  test/plexus_tests/common/utils/testutils_test.py
@@ -4,13 +4,12 @@ import unittest
4
4
  import ddt
5
5
 
6
6
  from plexus.common.utils.jsonutils import read_chunked_jsonl
7
- from plexus_test import resources_directory
7
+ from testenv import resources_directory
8
8
 
9
9
 
10
10
  @ddt.ddt
11
11
  class JsonUtilsTest(unittest.TestCase):
12
12
 
13
13
  def test_read_chunked_jsonl(self):
14
- for data, path in read_chunked_jsonl(
15
- os.path.join(resources_directory, "unittest/jsonutils", "dummy.{{}}.jsonl")):
14
+ for data, path in read_chunked_jsonl(str(resources_directory / "unittest" / "jsonutils" / "dummy.{{}}.jsonl")):
16
15
  self.assertEqual(data["file"], os.path.basename(path))
@@ -30,9 +30,13 @@ from plexus.common.utils.ormutils import (
30
30
  db_activate_revision_model,
31
31
  db_activate_snapshot_model,
32
32
  db_create_changing_model,
33
+ db_create_changing_models,
33
34
  db_create_revision_model,
35
+ db_create_revision_models,
34
36
  db_create_sequence_model,
37
+ db_create_sequence_models,
35
38
  db_create_snapshot_model,
39
+ db_create_snapshot_models,
36
40
  db_delete_sequence_model,
37
41
  db_expire_revision_model,
38
42
  db_expire_snapshot_model,
@@ -223,6 +227,21 @@ def test_db_sequence_model_crud(fixture_postgresql_test_proc, fixture_postgresql
223
227
  results = db_read_sequence_models(session, DummySequenceModel, 0, 200)
224
228
  assert len(results) == 0
225
229
 
230
+ create_records = [random_record() for _ in range(0, 100)]
231
+
232
+ results = db_create_sequence_models(session, DummySequenceModel, create_records)
233
+ assert len(results) == len(create_records)
234
+
235
+ for i, result in zip(range(0, 100), results):
236
+ assert result.sqn == i + 101
237
+ assert result.dummy_uuid == create_records[i].dummy_uuid
238
+ assert result.dummy_int == create_records[i].dummy_int
239
+ assert result.dummy_str == create_records[i].dummy_str
240
+ assert result.dummy_float == create_records[i].dummy_float
241
+ assert result.dummy_bool == create_records[i].dummy_bool
242
+ assert result.dummy_array == create_records[i].dummy_array
243
+ assert result.dummy_json == create_records[i].dummy_json
244
+
226
245
 
227
246
  def test_db_changing_model_crud(fixture_postgresql_test_proc, fixture_postgresql_test):
228
247
  scheme = make_scheme(Dialects.postgresql, Drivers.psycopg)
@@ -359,6 +378,26 @@ def test_db_changing_model_crud(fixture_postgresql_test_proc, fixture_postgresql
359
378
  results = db_read_sequence_models(session, DummyChangingModel, 0, 200)
360
379
  assert len(results) == 0
361
380
 
381
+ create_records = [random_record() for _ in range(0, 100)]
382
+
383
+ results = db_create_changing_models(session,
384
+ DummyChangingModel,
385
+ create_records,
386
+ dt_parse_iso("2024-01-01T00:00:00+00:00"))
387
+ assert len(results) == len(create_records)
388
+
389
+ for i, result in zip(range(0, 100), results):
390
+ assert result.sqn == i + 101
391
+ assert result.created_at == dt_parse_iso("2024-01-01T00:00:00+00:00")
392
+ assert result.updated_at == dt_parse_iso("2024-01-01T00:00:00+00:00")
393
+ assert result.dummy_uuid == create_records[i].dummy_uuid
394
+ assert result.dummy_int == create_records[i].dummy_int
395
+ assert result.dummy_str == create_records[i].dummy_str
396
+ assert result.dummy_float == create_records[i].dummy_float
397
+ assert result.dummy_bool == create_records[i].dummy_bool
398
+ assert result.dummy_array == create_records[i].dummy_array
399
+ assert result.dummy_json == create_records[i].dummy_json
400
+
362
401
 
363
402
  def test_db_snapshot_model_crud(fixture_postgresql_test_proc, fixture_postgresql_test):
364
403
  scheme = make_scheme(Dialects.postgresql, Drivers.psycopg)
@@ -644,6 +683,27 @@ def test_db_snapshot_model_crud(fixture_postgresql_test_proc, fixture_postgresql
644
683
  assert result.dummy_array == update_records[i].dummy_array
645
684
  assert result.dummy_json == update_records[i].dummy_json
646
685
 
686
+ create_records = [random_record() for _ in range(0, 100)]
687
+
688
+ results = db_create_snapshot_models(session,
689
+ DummySnapshotModel,
690
+ create_records,
691
+ dt_parse_iso("2024-01-01T00:00:00+00:00"))
692
+ assert len(results) == len(create_records)
693
+
694
+ for i, result in zip(range(0, 100), results):
695
+ assert result.sqn == i + 301
696
+ assert result.record_sqn == i + 301
697
+ assert result.created_at == dt_parse_iso("2024-01-01T00:00:00+00:00")
698
+ assert result.expired_at is None
699
+ assert result.dummy_uuid == create_records[i].dummy_uuid
700
+ assert result.dummy_int == create_records[i].dummy_int
701
+ assert result.dummy_str == create_records[i].dummy_str
702
+ assert result.dummy_float == create_records[i].dummy_float
703
+ assert result.dummy_bool == create_records[i].dummy_bool
704
+ assert result.dummy_array == create_records[i].dummy_array
705
+ assert result.dummy_json == create_records[i].dummy_json
706
+
647
707
 
648
708
  def test_db_revision_model_crud(fixture_postgresql_test_proc, fixture_postgresql_test):
649
709
  scheme = make_scheme(Dialects.postgresql, Drivers.psycopg)
@@ -957,6 +1017,29 @@ def test_db_revision_model_crud(fixture_postgresql_test_proc, fixture_postgresql
957
1017
  assert result.dummy_array == update_records[i].dummy_array
958
1018
  assert result.dummy_json == update_records[i].dummy_json
959
1019
 
1020
+ create_records = [random_record() for _ in range(0, 100)]
1021
+
1022
+ results = db_create_revision_models(session,
1023
+ DummyRevisionModel,
1024
+ create_records,
1025
+ dt_parse_iso("2024-01-01T00:00:00+00:00"))
1026
+ assert len(results) == len(create_records)
1027
+
1028
+ for i, result in zip(range(0, 100), results):
1029
+ assert result.sqn == i + 301
1030
+ assert result.record_sqn == i + 301
1031
+ assert result.revision == 1
1032
+ assert result.created_at == dt_parse_iso("2024-01-01T00:00:00+00:00")
1033
+ assert result.updated_at == dt_parse_iso("2024-01-01T00:00:00+00:00")
1034
+ assert result.expired_at is None
1035
+ assert result.dummy_uuid == create_records[i].dummy_uuid
1036
+ assert result.dummy_int == create_records[i].dummy_int
1037
+ assert result.dummy_str == create_records[i].dummy_str
1038
+ assert result.dummy_float == create_records[i].dummy_float
1039
+ assert result.dummy_bool == create_records[i].dummy_bool
1040
+ assert result.dummy_array == create_records[i].dummy_array
1041
+ assert result.dummy_json == create_records[i].dummy_json
1042
+
960
1043
 
961
1044
  def test_make_snapshot_model_trigger(fixture_postgresql_test_proc, fixture_postgresql_test):
962
1045
  scheme = make_scheme(Dialects.postgresql, Drivers.psycopg)