plexus-python-common 1.0.40__tar.gz → 1.0.41__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 (87) hide show
  1. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/PKG-INFO +1 -1
  2. plexus_python_common-1.0.41/src/plexus/common/resources/tags/__init__.py +20 -0
  3. plexus_python_common-1.0.41/src/plexus/common/resources/tags/universal.tagset.yaml +176 -0
  4. plexus_python_common-1.0.41/src/plexus/common/utils/__init__.py +0 -0
  5. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/datautils.py +189 -2
  6. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/jsonutils.py +1 -1
  7. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/s3utils.py +1 -1
  8. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  9. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/SOURCES.txt +3 -0
  10. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/datautils_test.py +13 -0
  11. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/.editorconfig +0 -0
  12. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/.github/workflows/pr.yml +0 -0
  13. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/.github/workflows/push.yml +0 -0
  14. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/.gitignore +0 -0
  15. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/MANIFEST.in +0 -0
  16. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/README.md +0 -0
  17. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/VERSION +0 -0
  18. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/pyproject.toml +0 -0
  19. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  20. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  21. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  22. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  23. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  24. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  25. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  26. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  27. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  28. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  29. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  30. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  31. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  32. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  33. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  34. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/0-dummy +0 -0
  35. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/1-dummy +0 -0
  36. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/2-dummy +0 -0
  37. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.0.0.jsonl +0 -0
  38. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.0.0.vol-0.jsonl +0 -0
  39. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.0.jsonl +0 -0
  40. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.1.1.jsonl +0 -0
  41. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.1.1.vol-1.jsonl +0 -0
  42. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.1.jsonl +0 -0
  43. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.2.2.jsonl +0 -0
  44. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.2.2.vol-2.jsonl +0 -0
  45. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.2.jsonl +0 -0
  46. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.csv.part0 +0 -0
  47. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.csv.part1 +0 -0
  48. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.csv.part2 +0 -0
  49. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/resources/unittest/shutils/dummy.txt +0 -0
  50. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/setup.cfg +0 -0
  51. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/setup.py +0 -0
  52. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/__init__.py +0 -0
  53. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/carto/OSMFile.py +0 -0
  54. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/carto/OSMNode.py +0 -0
  55. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/carto/OSMTags.py +0 -0
  56. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/carto/OSMWay.py +0 -0
  57. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/carto/__init__.py +0 -0
  58. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/pose.py +0 -0
  59. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/proj.py +0 -0
  60. {plexus_python_common-1.0.40/src/plexus/common/utils → plexus_python_common-1.0.41/src/plexus/common/resources}/__init__.py +0 -0
  61. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/apiutils.py +0 -0
  62. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/bagutils.py +0 -0
  63. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/config.py +0 -0
  64. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/dockerutils.py +0 -0
  65. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/ormutils.py +0 -0
  66. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/shutils.py +0 -0
  67. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/sqlutils.py +0 -0
  68. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/strutils.py +0 -0
  69. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus/common/utils/testutils.py +0 -0
  70. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  71. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  72. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/requires.txt +0 -0
  73. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  74. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_test.py +0 -0
  75. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/__init__.py +0 -0
  76. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  77. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  78. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/pose_test.py +0 -0
  79. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/proj_test.py +0 -0
  80. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  81. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  82. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  83. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  84. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  85. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/shutils_test.py +0 -0
  86. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  87. {plexus_python_common-1.0.40 → plexus_python_common-1.0.41}/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.40
3
+ Version: 1.0.41
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,20 @@
1
+ from pathlib import Path
2
+
3
+ import yaml
4
+ from iker.common.utils.funcutils import singleton
5
+ from iker.common.utils.jsonutils import JsonObject
6
+
7
+ __all__ = [
8
+ "predefined_tagset_specs",
9
+ ]
10
+
11
+
12
+ @singleton
13
+ def predefined_tagset_specs() -> list[tuple[Path, JsonObject]]:
14
+ def load():
15
+ root = Path(__file__).parent
16
+ for path in root.glob("**/*.tagset.yaml"):
17
+ text = path.read_text(encoding="utf-8")
18
+ yield path, yaml.safe_load(text)
19
+
20
+ return list(load())
@@ -0,0 +1,176 @@
1
+ $namespace: "universal"
2
+ $desc: "Universal tags applicable across various scenarios."
3
+ $def:
4
+ camera_views:
5
+ &camera_views
6
+ unified: { $desc: "unified" }
7
+ front_center: { $desc: "front center" }
8
+ front_left: { $desc: "front left" }
9
+ front_right: { $desc: "front right" }
10
+ side_left: { $desc: "side left" }
11
+ side_right: { $desc: "side right" }
12
+ rear_left: { $desc: "rear left" }
13
+ rear_right: { $desc: "rear right" }
14
+ radar_views:
15
+ &radar_views
16
+ unified: { $desc: "unified" }
17
+ bumper_center: { $desc: "bumper center" }
18
+ bumper_left: { $desc: "bumper left" }
19
+ bumper_right: { $desc: "bumper right" }
20
+ lidar_views:
21
+ &lidar_views
22
+ unified: { $desc: "unified" }
23
+ front_center: { $desc: "front center" }
24
+ front_left: { $desc: "front left" }
25
+ front_right: { $desc: "front right" }
26
+ side_left: { $desc: "side left" }
27
+ side_right: { $desc: "side right" }
28
+ rear_left: { $desc: "rear left" }
29
+ rear_right: { $desc: "rear right" }
30
+ $tags:
31
+ environment:
32
+ tod:
33
+ dawn: { $desc: "dawn" }
34
+ daytime: { $desc: "daytime" }
35
+ dusk: { $desc: "dusk" }
36
+ nighttime: { $desc: "nighttime" }
37
+ unknown: { $desc: "unknown" }
38
+ weather:
39
+ cloudy: { $desc: "cloudy" }
40
+ foggy: { $desc: "foggy" }
41
+ rainy: { $desc: "rainy" }
42
+ snowy: { $desc: "snowy" }
43
+ unknown: { $desc: "unknown" }
44
+ visibility:
45
+ high: { $desc: "high" }
46
+ moderate: { $desc: "moderate" }
47
+ low: { $desc: "low" }
48
+ unknown: { $desc: "unknown" }
49
+ obstructions:
50
+ fog: { $desc: "fog" }
51
+ smoke: { $desc: "smoke" }
52
+ dust: { $desc: "dust" }
53
+ snowfall: { $desc: "snowfall" }
54
+ rainfall: { $desc: "rainfall" }
55
+ unknown: { $desc: "unknown" }
56
+ lighting:
57
+ normal: { $desc: "normal ambient lighting" }
58
+ low_light: { $desc: "low ambient lighting" }
59
+ glare: { $desc: "glare from sun or artificial source" }
60
+ shadows: { $desc: "shadows casting on the roadway" }
61
+ unknown: { $desc: "unknown" }
62
+ road_surface:
63
+ dry: { $desc: "dry" }
64
+ wet: { $desc: "wet" }
65
+ standing_water: { $desc: "standing water" }
66
+ icy: { $desc: "icy" }
67
+ snow_covered: { $desc: "snow covered" }
68
+ slush: { $desc: "covered in slush" }
69
+ gravel: { $desc: "covered in gravel" }
70
+ contaminated: { $desc: "contaminated with oil, mud, etc." }
71
+ unknown: { $desc: "unknown" }
72
+ roadway:
73
+ classification:
74
+ expressway: { $desc: "expressway/freeway" }
75
+ highway: { $desc: "highway/major road" }
76
+ arterial: { $desc: "arterial road" }
77
+ collector: { $desc: "collector road" }
78
+ residential: { $desc: "residential/street" }
79
+ rural: { $desc: "rural road" }
80
+ unpaved: { $desc: "unpaved road" }
81
+ service_road: { $desc: "service road/alley" }
82
+ parking_lot: { $desc: "parking lot/garage" }
83
+ unknown: { $desc: "unknown" }
84
+ layout:
85
+ single_way: { $desc: "single way" }
86
+ undivided_two_way: { $desc: "undivided two way" }
87
+ divided_two_way: { $desc: "divided two way" }
88
+ unknown: { $desc: "unknown" }
89
+ lane_config:
90
+ single_lane: { $desc: "single lane" }
91
+ two_lane: { $desc: "two lane" }
92
+ multi_lane: { $desc: "multi lane" }
93
+ unknown: { $desc: "unknown" }
94
+ shoulder:
95
+ none: { $desc: "no shoulder" }
96
+ median: { $desc: "median shoulder" }
97
+ verge: { $desc: "verge shoulder" }
98
+ sidewalk: { $desc: "sidewalk shoulder" }
99
+ emergency_lane: { $desc: "emergency lane" }
100
+ unknown: { $desc: "unknown" }
101
+ junction:
102
+ cross: { $desc: "cross intersection" }
103
+ tee_left_right: { $desc: "tee intersection left and right" }
104
+ tee_left_only: { $desc: "tee intersection left only and straight" }
105
+ tee_right_only: { $desc: "tee intersection right only and straight" }
106
+ y_intersection: { $desc: "y intersection" }
107
+ roundabout: { $desc: "roundabout" }
108
+ merge: { $desc: "merge" }
109
+ split: { $desc: "split" }
110
+ other: { $desc: "other junction type" }
111
+ unknown: { $desc: "unknown" }
112
+ infrastructure:
113
+ tunnel: { $desc: "tunnel" }
114
+ bridge: { $desc: "bridge/overpass" }
115
+ underpass: { $desc: "underpass" }
116
+ unknown: { $desc: "unknown" }
117
+ geometry:
118
+ curved: { $desc: "curved segment" }
119
+ incline: { $desc: "incline segment" }
120
+ decline: { $desc: "decline segment" }
121
+ hill_crest: { $desc: "hill crest" }
122
+ sag: { $desc: "sag segment" }
123
+ traffic:
124
+ density:
125
+ absent: { $desc: "no traffic" }
126
+ sparse: { $desc: "sparse traffic" }
127
+ light: { $desc: "light traffic" }
128
+ moderate: { $desc: "moderate traffic" }
129
+ dense: { $desc: "dense traffic" }
130
+ jammed: { $desc: "traffic jam" }
131
+ unknown: { $desc: "unknown" }
132
+ pedestrian_presence:
133
+ absent: { $desc: "no pedestrians" }
134
+ sparse: { $desc: "sparse pedestrians" }
135
+ moderate: { $desc: "moderate pedestrians" }
136
+ dense: { $desc: "dense pedestrians" }
137
+ unknown: { $desc: "unknown" }
138
+ cyclist_presence:
139
+ absent: { $desc: "no cyclists" }
140
+ sparse: { $desc: "sparse cyclists" }
141
+ moderate: { $desc: "moderate cyclists" }
142
+ dense: { $desc: "dense cyclists" }
143
+ unknown: { $desc: "unknown" }
144
+ construction_zone:
145
+ absent: { $desc: "no construction" }
146
+ minor: { $desc: "minor construction" }
147
+ major: { $desc: "major construction" }
148
+ unknown: { $desc: "unknown" }
149
+ accident:
150
+ road_block: { $desc: "road block" }
151
+ debris_on_road: { $desc: "debris on road" }
152
+ animal_on_road: { $desc: "animal on road" }
153
+ pedestrian_on_road: { $desc: "pedestrian on road" }
154
+ vehicle_breakdown: { $desc: "vehicle breakdown" }
155
+ emergency_vehicle_present: { $desc: "emergency vehicle present" }
156
+ police_stop: { $desc: "police stop" }
157
+ other: { $desc: "other accident type" }
158
+ data_fidelity:
159
+ camera_issue:
160
+ glare: { $desc: "glare from sun or artificial source", <<: *camera_views }
161
+ motion_blur: { $desc: "motion blur", <<: *camera_views }
162
+ lens_smear: { $desc: "lens smear or dirt", <<: *camera_views }
163
+ occlusion: { $desc: "occlusion by objects", <<: *camera_views }
164
+ underexposure: { $desc: "underexposure", <<: *camera_views }
165
+ overexposure: { $desc: "overexposure", <<: *camera_views }
166
+ radar_issue:
167
+ weather_effects: { $desc: "weather effects", <<: *radar_views }
168
+ interference: { $desc: "interference", <<: *radar_views }
169
+ multipath: { $desc: "multipath reflections", <<: *radar_views }
170
+ clutter: { $desc: "clutter", <<: *radar_views }
171
+ lidar_issue:
172
+ weather_effects: { $desc: "weather effects", <<: *lidar_views }
173
+ occlusion: { $desc: "occlusion by objects", <<: *lidar_views }
174
+ motion_distortion: { $desc: "motion distortion", <<: *lidar_views }
175
+ range_dropout: { $desc: "range dropout", <<: *lidar_views }
176
+ reflectivity: { $desc: "reflectivity issues", <<: *lidar_views }
@@ -1,13 +1,19 @@
1
+ import dataclasses
1
2
  import datetime
2
- from collections.abc import Callable, Generator
3
+ from collections.abc import Callable, Generator, Mapping, Sequence
3
4
  from typing import Any
4
5
 
5
6
  import pyparsing as pp
6
7
  import ujson as json
7
8
  from iker.common.utils.funcutils import singleton
8
- from iker.common.utils.jsonutils import JsonType
9
+ from iker.common.utils.iterutils import dicttree
10
+ from iker.common.utils.iterutils import dicttree_add, dicttree_remove
11
+ from iker.common.utils.iterutils import dicttree_children, dicttree_lineage, dicttree_subtree
12
+ from iker.common.utils.iterutils import head_or_none
13
+ from iker.common.utils.jsonutils import JsonObject, JsonType
9
14
  from iker.common.utils.randutils import randomizer
10
15
 
16
+ from plexus.common.resources.tags import predefined_tagset_specs
11
17
  from plexus.common.utils.strutils import BagName, UserName, VehicleName
12
18
  from plexus.common.utils.strutils import colon_tag_parser, slash_tag_parser
13
19
  from plexus.common.utils.strutils import dot_case_parser, kebab_case_parser, snake_case_parser
@@ -18,6 +24,38 @@ from plexus.common.utils.strutils import strict_abspath_parser, strict_relpath_p
18
24
  from plexus.common.utils.strutils import strict_fragmented_abspath_parser, strict_fragmented_relpath_parser
19
25
  from plexus.common.utils.strutils import topic_parser, vin_code_chars, vin_code_parser
20
26
 
27
+ __all__ = [
28
+ "compute_vin_code_check_digit",
29
+ "validate_hex_string",
30
+ "validate_snake_case",
31
+ "validate_kebab_case",
32
+ "validate_dot_case",
33
+ "validate_uuid",
34
+ "validate_strict_relpath",
35
+ "validate_strict_abspath",
36
+ "validate_strict_fragmented_relpath",
37
+ "validate_strict_fragmented_abspath",
38
+ "validate_semver",
39
+ "validate_colon_tag",
40
+ "validate_slash_tag",
41
+ "validate_topic",
42
+ "validate_vin_code",
43
+ "validate_user_name",
44
+ "validate_vehicle_name",
45
+ "validate_bag_name",
46
+ "validate_dt_timezone",
47
+ "validate_json_type_dump_size",
48
+ "random_vin_code",
49
+ "known_topics",
50
+ "known_user_names",
51
+ "known_vehicle_names",
52
+ "random_bag_names_sequence",
53
+ "Tag",
54
+ "Tagset",
55
+ "populate_tagset",
56
+ "predefined_tagsets",
57
+ ]
58
+
21
59
 
22
60
  def make_compute_vin_code_check_digit() -> Callable[[str], str]:
23
61
  trans_nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 7, 9, 2, 3, 4, 5, 6, 7, 8, 9]
@@ -201,3 +239,152 @@ def random_bag_names_sequence(
201
239
 
202
240
  for record_sn in range(bags_count):
203
241
  yield BagName(vehicle_name=vehicle_name, record_dt=record_dt, record_sn=record_sn)
242
+
243
+
244
+ @dataclasses.dataclass(frozen=True, eq=True, order=True)
245
+ class Tag(object):
246
+ name: str
247
+ desc: str | None
248
+
249
+ @property
250
+ def tag_parts(self) -> list[str]:
251
+ return self.name.rsplit(":")
252
+
253
+ @property
254
+ def parent_tag_name(self) -> str | None:
255
+ return head_or_none(self.name.rsplit(":", 1))
256
+
257
+
258
+ class Tagset(Sequence[Tag], Mapping[str, Tag]):
259
+ def __init__(self, namespace: str, desc: str) -> None:
260
+ super().__init__()
261
+ self.namespace = namespace
262
+ self.desc = desc
263
+ self.tags: list[Tag] = []
264
+ self.tags_dict: dict[str, Tag] = {}
265
+ self.tags_tree: dicttree[str, Tag] = {}
266
+
267
+ def __contains__(self, item: str | Tag) -> bool:
268
+ tag_name = item.name if isinstance(item, Tag) else item
269
+ return tag_name in self.tags_dict
270
+
271
+ def __len__(self) -> int:
272
+ return len(self.tags)
273
+
274
+ def __getitem__(self, index: int) -> Tag:
275
+ return self.tags[index]
276
+
277
+ def keys(self):
278
+ return self.tags_dict.keys()
279
+
280
+ def values(self):
281
+ return self.tags_dict.values()
282
+
283
+ def items(self):
284
+ return self.tags_dict.items()
285
+
286
+ def get(self, item: str | Tag) -> Tag | None:
287
+ tag_name = item.name if isinstance(item, Tag) else item
288
+ return self.tags_dict.get(tag_name)
289
+
290
+ @property
291
+ def tag_names(self) -> list[str]:
292
+ return list(self.tags_dict.keys())
293
+
294
+ def add(self, tag: Tag) -> None:
295
+ if tag.name in self.tags_dict:
296
+ raise ValueError(f"duplicate tag name '{tag.name}'")
297
+
298
+ self.tags.append(tag)
299
+ self.tags_dict[tag.name] = tag
300
+
301
+ dicttree_add(self.tags_tree, tag.tag_parts, tag, create_prefix=True)
302
+
303
+ def remove(self, tag_name: str) -> None:
304
+ tag = self.get(tag_name)
305
+ if tag is None:
306
+ raise ValueError(f"tag '{tag_name}' not found")
307
+
308
+ self.tags.remove(tag)
309
+ del self.tags_dict[tag_name]
310
+
311
+ dicttree_remove(self.tags_tree, tag.tag_parts, recursive=True)
312
+
313
+ def child_tags(self, parent: str | Tag) -> list[Tag]:
314
+ if parent is None:
315
+ return []
316
+ if isinstance(parent, str):
317
+ return self.child_tags(self.get(parent))
318
+
319
+ subtree = dicttree_subtree(self.tags_tree, parent.tag_parts)
320
+ return list(dicttree_children(subtree)) if subtree else []
321
+
322
+ def parent_tags(self, child: str | Tag) -> list[Tag]:
323
+ if child is None:
324
+ return []
325
+ if isinstance(child, str):
326
+ return self.parent_tags(self.get(child))
327
+
328
+ return list(dicttree_lineage(self.tags_tree, child.tag_parts[:-1]))
329
+
330
+
331
+ def populate_tagset(tagset_spec: JsonObject) -> Tagset:
332
+ """
333
+ Collect tags from tagset spec JSON object, validate the format along the way.
334
+
335
+ :param tagset_spec: JSON object of tagset spec
336
+ :return: Populated Tagset instance
337
+ """
338
+
339
+ def validate_and_collect(tag_name: str, tag_def: JsonObject) -> Generator[Tag, None, None]:
340
+ if not isinstance(tag_def, dict):
341
+ raise ValueError(f"tag '{tag_name}' definition is not a dict")
342
+
343
+ for child_tag_name, child_tag_def in tag_def.items():
344
+ if child_tag_name == "$desc":
345
+ continue
346
+
347
+ if not isinstance(child_tag_name, str):
348
+ raise ValueError(f"child '{child_tag_name}' of tag '{tag_name}' is not a string")
349
+ try:
350
+ validate_snake_case(child_tag_name)
351
+ except ValueError as e:
352
+ raise ValueError(f"child '{child_tag_name}' of tag '{tag_name}' is not in snake case") from e
353
+
354
+ child_tag_name = tag_name + ":" + child_tag_name if tag_name else child_tag_name
355
+
356
+ yield from validate_and_collect(child_tag_name, child_tag_def)
357
+
358
+ yield Tag(name=tag_name, desc=tag_def.get("$desc"))
359
+
360
+ namespace = tagset_spec.get("$namespace")
361
+ if namespace is None:
362
+ raise ValueError("missing '$namespace' in tagset spec")
363
+ try:
364
+ validate_snake_case(namespace)
365
+ except ValueError as e:
366
+ raise ValueError(f"tagset namespace '{namespace}' is not in snake case") from e
367
+
368
+ desc = tagset_spec.get("$desc")
369
+ if desc is None:
370
+ raise ValueError("missing '$desc' in tagset spec")
371
+
372
+ tags = tagset_spec.get("$tags")
373
+ if tags is None:
374
+ raise ValueError("missing '$tags' in tagset spec")
375
+
376
+ tagset = Tagset(namespace=namespace, desc=desc)
377
+
378
+ for tag in validate_and_collect("", tags):
379
+ tagset.add(tag)
380
+
381
+ return tagset
382
+
383
+
384
+ @singleton
385
+ def predefined_tagsets() -> dict[str, Tagset]:
386
+ tagsets: dict[str, Tagset] = {}
387
+ for _, tagset_spec in predefined_tagset_specs():
388
+ tagset = populate_tagset(tagset_spec)
389
+ tagsets[tagset.namespace] = tagset
390
+ return tagsets
@@ -5,9 +5,9 @@ from typing import Any
5
5
 
6
6
  import ujson as json
7
7
  from iker.common.utils.dtutils import dt_format, dt_parse, extended_format
8
+ from iker.common.utils.iterutils import batched
8
9
  from iker.common.utils.jsonutils import JsonType, JsonValueCompatible
9
10
  from iker.common.utils.jsonutils import json_reformat
10
- from iker.common.utils.sequtils import batched
11
11
 
12
12
  from plexus.common.utils.shutils import collect_volumed_filenames, populate_volumed_filenames
13
13
 
@@ -18,8 +18,8 @@ from typing import Literal
18
18
  import boto3
19
19
  import fsspec
20
20
  import fsspec.utils
21
+ from iker.common.utils.iterutils import chunk_between, head, last
21
22
  from iker.common.utils.jsonutils import JsonObject
22
- from iker.common.utils.sequtils import chunk_between, head, last
23
23
  from iker.common.utils.shutils import glob_match, listfile, path_depth
24
24
  from iker.common.utils.strutils import is_empty, trim_to_none
25
25
  from mypy_boto3_s3 import S3Client
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.40
3
+ Version: 1.0.41
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -46,6 +46,9 @@ src/plexus/common/carto/OSMNode.py
46
46
  src/plexus/common/carto/OSMTags.py
47
47
  src/plexus/common/carto/OSMWay.py
48
48
  src/plexus/common/carto/__init__.py
49
+ src/plexus/common/resources/__init__.py
50
+ src/plexus/common/resources/tags/__init__.py
51
+ src/plexus/common/resources/tags/universal.tagset.yaml
49
52
  src/plexus/common/utils/__init__.py
50
53
  src/plexus/common/utils/apiutils.py
51
54
  src/plexus/common/utils/bagutils.py
@@ -2,6 +2,8 @@ import unittest
2
2
 
3
3
  import ddt
4
4
 
5
+ from plexus.common.utils.datautils import Tagset
6
+ from plexus.common.utils.datautils import predefined_tagsets
5
7
  from plexus.common.utils.datautils import random_vin_code, validate_vin_code
6
8
 
7
9
 
@@ -36,3 +38,14 @@ class DataUtilsTest(unittest.TestCase):
36
38
  def test_validate_vin_code__bad_case(self, data):
37
39
  with self.assertRaises(ValueError):
38
40
  validate_vin_code(data)
41
+
42
+ def test_predefined_tagsets(self):
43
+ tagsets = predefined_tagsets()
44
+ for _, tagset in tagsets.items():
45
+ self.assertIsInstance(tagset, Tagset)
46
+
47
+ for tag in tagset:
48
+ self.assertIn(tag, tagset)
49
+ self.assertIn(tag.name, tagset)
50
+ self.assertEqual(tag, tagset.get(tag))
51
+ self.assertEqual(tag, tagset.get(tag.name))