plexus-python-common 1.0.64__tar.gz → 1.0.65__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 (91) hide show
  1. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/PKG-INFO +1 -1
  2. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/tagutils.py +95 -13
  3. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/PKG-INFO +1 -1
  4. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/.editorconfig +0 -0
  5. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/.github/workflows/pr.yml +0 -0
  6. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/.github/workflows/push.yml +0 -0
  7. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/.gitignore +0 -0
  8. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/MANIFEST.in +0 -0
  9. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/README.md +0 -0
  10. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/VERSION +0 -0
  11. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/pyproject.toml +0 -0
  12. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  13. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  14. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  15. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/0-dummy +0 -0
  16. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/1-dummy +0 -0
  17. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/2-dummy +0 -0
  18. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  19. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  20. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  21. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  22. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  23. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  24. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  25. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  26. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  27. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  28. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  29. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  30. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/pathutils/dummy.txt +0 -0
  31. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  32. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  33. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  34. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  35. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  36. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  37. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  38. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  39. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  40. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  41. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  42. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  43. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/setup.cfg +0 -0
  44. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/setup.py +0 -0
  45. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/__init__.py +0 -0
  46. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/carto/OSMFile.py +0 -0
  47. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/carto/OSMNode.py +0 -0
  48. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/carto/OSMTags.py +0 -0
  49. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/carto/OSMWay.py +0 -0
  50. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/carto/__init__.py +0 -0
  51. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/resources/__init__.py +0 -0
  52. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/resources/tags/__init__.py +0 -0
  53. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
  54. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
  55. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/__init__.py +0 -0
  56. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/apiutils.py +0 -0
  57. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/bagutils.py +0 -0
  58. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/config.py +0 -0
  59. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/datautils.py +0 -0
  60. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/dockerutils.py +0 -0
  61. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/gisutils.py +0 -0
  62. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/jsonutils.py +0 -0
  63. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/ormutils.py +0 -0
  64. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/pathutils.py +0 -0
  65. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/s3utils.py +0 -0
  66. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/sqlutils.py +0 -0
  67. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/strutils.py +0 -0
  68. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus/common/utils/testutils.py +0 -0
  69. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/SOURCES.txt +0 -0
  70. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  71. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  72. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/requires.txt +0 -0
  73. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  74. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/__init__.py +0 -0
  75. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/__init__.py +0 -0
  76. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/carto/__init__.py +0 -0
  77. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  78. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  79. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/__init__.py +0 -0
  80. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  81. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  82. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/dockerutils_test.py +0 -0
  83. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/gisutils_test.py +0 -0
  84. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  85. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/ormutils_test.py +0 -0
  86. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/pathutils_test.py +0 -0
  87. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  88. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/strutils_test.py +0 -0
  89. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/tagutils_test.py +0 -0
  90. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/test/plexus_tests/common/utils/testutils_test.py +0 -0
  91. {plexus_python_common-1.0.64 → plexus_python_common-1.0.65}/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.64
3
+ Version: 1.0.65
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -18,7 +18,8 @@ import sqlalchemy.dialects.sqlite as sa_sqlite
18
18
  import sqlalchemy.orm as sa_orm
19
19
  from iker.common.utils.dbutils import ConnectionMaker
20
20
  from iker.common.utils.dtutils import dt_from_ts_us, dt_to_ts_us
21
- from iker.common.utils.funcutils import memorized, singleton
21
+ from iker.common.utils.funcutils import Chainable
22
+ from iker.common.utils.funcutils import chainable, memorized, singleton
22
23
  from iker.common.utils.iterutils import batched, head_or_none
23
24
  from iker.common.utils.iterutils import dicttree
24
25
  from iker.common.utils.iterutils import dicttree_add, dicttree_remove
@@ -31,6 +32,7 @@ from sqlmodel import Field, SQLModel
31
32
  from plexus.common.resources.tags import predefined_tagset_specs
32
33
  from plexus.common.utils.datautils import validate_colon_tag, validate_snake_case, validate_vehicle_name
33
34
  from plexus.common.utils.datautils import validate_dt_timezone, validate_semver, validate_slash_tag
35
+ from plexus.common.utils.jsonutils import json_datetime_encoder
34
36
  from plexus.common.utils.ormutils import SequenceModelMixinProtocol
35
37
  from plexus.common.utils.ormutils import clone_sequence_model_instance, make_base_model, make_sequence_model_mixin
36
38
  from plexus.common.utils.sqlutils import escape_sql_like
@@ -444,6 +446,14 @@ class TagTarget(BaseModel):
444
446
  raise ValueError(f"begin_dt '{self.begin_dt}' is greater than end_dt '{self.end_dt}'")
445
447
  return self
446
448
 
449
+ @pdt.field_serializer("begin_dt", mode="plain")
450
+ def serialize_begin_dt(self, v: datetime.datetime) -> str:
451
+ return json_datetime_encoder(v)
452
+
453
+ @pdt.field_serializer("end_dt", mode="plain")
454
+ def serialize_end_dt(self, v: datetime.datetime) -> str:
455
+ return json_datetime_encoder(v)
456
+
447
457
 
448
458
  class TagRecord(BaseModel):
449
459
  target_sqn: int = Field(
@@ -477,6 +487,11 @@ class TagRecord(BaseModel):
477
487
  default=None,
478
488
  description="Additional properties of the tag record in JSON format",
479
489
  )
490
+ flags: int = Field(
491
+ sa_column=sa.Column(sa_sqlite.INTEGER),
492
+ default=0,
493
+ description="Integer bitmask storing status or metadata flags for this tag record",
494
+ )
480
495
 
481
496
  @pdt.field_validator("begin_dt", mode="after")
482
497
  @classmethod
@@ -516,6 +531,14 @@ class TagRecord(BaseModel):
516
531
  validate_colon_tag(v)
517
532
  return v
518
533
 
534
+ @pdt.field_serializer("begin_dt", mode="plain")
535
+ def serialize_begin_dt(self, v: datetime.datetime) -> str:
536
+ return json_datetime_encoder(v)
537
+
538
+ @pdt.field_serializer("end_dt", mode="plain")
539
+ def serialize_end_dt(self, v: datetime.datetime) -> str:
540
+ return json_datetime_encoder(v)
541
+
519
542
 
520
543
  class TagTargetTable(TagTarget, make_sequence_model_mixin("sqlite"), table=True):
521
544
  __tablename__ = "tag_target_info"
@@ -543,6 +566,7 @@ if typing.TYPE_CHECKING:
543
566
  tagset_version: sa_orm.Mapped[str | None] = ...
544
567
  tag: sa_orm.Mapped[str] = ...
545
568
  props: sa_orm.Mapped[JsonType | None] = ...
569
+ flags: sa_orm.Mapped[int] = ...
546
570
 
547
571
 
548
572
  @singleton
@@ -575,9 +599,12 @@ class TagCache(object):
575
599
  with self.conn_maker.make_session() as session:
576
600
  yield session
577
601
 
578
- def get_target(self, identifier: str) -> TagTargetTable | None:
602
+ def get_target(self, target: int | str) -> TagTargetTable | None:
579
603
  with self.make_session() as session:
580
- return session.query(TagTargetTable).filter(TagTargetTable.identifier == identifier).one_or_none()
604
+ if isinstance(target, int):
605
+ return session.get(TagTargetTable, target)
606
+ else:
607
+ return session.query(TagTargetTable).filter(TagTargetTable.identifier == target).one_or_none()
581
608
 
582
609
  def query_targets(
583
610
  self,
@@ -664,10 +691,10 @@ class TagCache(object):
664
691
  )
665
692
  session.commit()
666
693
 
667
- def with_target(self, name: str) -> "TargetedTagCache":
668
- target_info = self.get_target(name)
694
+ def with_target(self, target: int | str) -> "TargetedTagCache":
695
+ target_info = self.get_target(target)
669
696
  if target_info is None:
670
- raise ValueError(f"target with name '{name}' not found in cache")
697
+ raise ValueError(f"target '{target}' not found in cache")
671
698
  return TargetedTagCache(cache=self, target_info=target_info)
672
699
 
673
700
  def iter_tags(
@@ -961,7 +988,7 @@ class TargetedTagCache(object):
961
988
  props: JsonType | None = None,
962
989
  tagset_namespace: str | None = None,
963
990
  tagset_version: str | None = None,
964
- ) -> Self:
991
+ ) -> Chainable[Self, TagRecordTable]:
965
992
  """
966
993
  Add a tag record to the cache for the specified time range. If ``begin_dt`` or ``end_dt`` is None, it will
967
994
  default to the target's ``begin_dt`` or ``end_dt`` respectively.
@@ -988,10 +1015,12 @@ class TargetedTagCache(object):
988
1015
  tag=tag.name if isinstance(tag, Tag) else tag,
989
1016
  props=props,
990
1017
  )
991
- session.add(clone_sequence_model_instance(TagRecordTable, tag_record))
1018
+ db_tag_record = clone_sequence_model_instance(TagRecordTable, tag_record)
1019
+ session.add(db_tag_record)
992
1020
  session.commit()
993
1021
 
994
- return self
1022
+ session.refresh(db_tag_record)
1023
+ return chainable(self, db_tag_record)
995
1024
 
996
1025
  def add_tag(
997
1026
  self,
@@ -999,7 +1028,7 @@ class TargetedTagCache(object):
999
1028
  props: JsonType | None = None,
1000
1029
  tagset_namespace: str | None = None,
1001
1030
  tagset_version: str | None = None,
1002
- ) -> Self:
1031
+ ) -> Chainable[Self, TagRecordTable]:
1003
1032
  """
1004
1033
  Add a tag record to the cache for the entire target range.
1005
1034
 
@@ -1023,6 +1052,61 @@ class TargetedTagCache(object):
1023
1052
  props=props,
1024
1053
  )
1025
1054
 
1055
+ def update_tag(
1056
+ self,
1057
+ sqn: int,
1058
+ *,
1059
+ begin_dt: datetime.datetime | None = None,
1060
+ end_dt: datetime.datetime | None = None,
1061
+ tag: str | Tag | BoundTag | None = None,
1062
+ props: JsonType | None = None,
1063
+ tagset_namespace: str | None = None,
1064
+ tagset_version: str | None = None,
1065
+ flags: int | None = None,
1066
+ ) -> Chainable[Self, TagRecordTable]:
1067
+ """
1068
+ Update a tag record in the cache by its sequence number.
1069
+
1070
+ :param sqn: Sequence number of the tag record to be updated
1071
+ :param begin_dt: New begin datetime of the tag record (optional)
1072
+ :param end_dt: New end datetime of the tag record (optional)
1073
+ :param tag: New tag name or ``Tag``/``BoundTag`` instance to be updated (optional). If ``Tag``/``BoundTag``
1074
+ instance is provided, its name will be used.
1075
+ :param props: New additional properties of the tag record in JSON format (optional)
1076
+ :param tagset_namespace: New namespace of the tagset that the tag belongs to (optional). If the ``tag``
1077
+ parameter is a ``BoundTag`` instance, this parameter will be ignored and the namespace
1078
+ from the instance will be used.
1079
+ :param tagset_version: New version of the tagset that the tag belongs to (optional). If the ``tag`` parameter
1080
+ is a ``BoundTag`` instance, this parameter will be ignored and the version from the
1081
+ instance will be used.
1082
+ :param flags: New integer bitmask storing status or metadata flags for this tag record (optional)
1083
+ :return: Self instance for chaining
1084
+ """
1085
+ with self.make_session() as session:
1086
+ db_tag_record = session.query(TagRecordTable).filter(TagRecordTable.sqn == sqn).one_or_none()
1087
+ if not db_tag_record:
1088
+ raise ValueError(f"tag record with sqn '{sqn}' not found in cache")
1089
+
1090
+ if begin_dt is not None:
1091
+ db_tag_record.begin_dt = begin_dt
1092
+ if end_dt is not None:
1093
+ db_tag_record.end_dt = end_dt
1094
+ if tagset_namespace is not None:
1095
+ db_tag_record.tagset_namespace = tag.namespace if isinstance(tag, BoundTag) else tagset_namespace
1096
+ if tagset_version is not None:
1097
+ db_tag_record.tagset_version = tag.version if isinstance(tag, BoundTag) else tagset_version
1098
+ if tag is not None:
1099
+ db_tag_record.tag = tag.name if isinstance(tag, Tag) else tag
1100
+ if props is not None:
1101
+ db_tag_record.props = props
1102
+ if flags is not None:
1103
+ db_tag_record.flags = flags
1104
+
1105
+ session.commit()
1106
+
1107
+ session.refresh(db_tag_record)
1108
+ return chainable(self, db_tag_record)
1109
+
1026
1110
  def remove_tags(
1027
1111
  self,
1028
1112
  begin_dt: datetime.datetime | None = None,
@@ -1099,14 +1183,12 @@ def tag_cache(*, identifier: str | None = None, file_path: str | None = None) ->
1099
1183
  return TagCache(file_path=tag_cache_file_path())
1100
1184
 
1101
1185
 
1102
-
1103
1186
  @singleton
1104
1187
  def standard_clip_duration_us() -> int:
1105
1188
  """Duration of clips to split samples into, in microseconds, which is fixed to 20 seconds for now."""
1106
1189
  return 20 * 1_000_000 # 20 seconds
1107
1190
 
1108
1191
 
1109
-
1110
1192
  def populate_clip_ranges(
1111
1193
  data_begin_dt: datetime.datetime,
1112
1194
  data_end_dt: datetime.datetime,
@@ -1137,7 +1219,7 @@ def populate_clip_ranges(
1137
1219
  return
1138
1220
 
1139
1221
  begin_clip_slot = math.floor(dt_to_ts_us(data_begin_dt) / clip_duration_us)
1140
- end_clip_slot = math.ceil(dt_to_ts_us(data_end_dt) / clip_duration_us)
1222
+ end_clip_slot = math.ceil(dt_to_ts_us(data_end_dt) / clip_duration_us)
1141
1223
 
1142
1224
  for clip_slot in range(begin_clip_slot, end_clip_slot):
1143
1225
  clip_begin_dt = dt_from_ts_us(clip_slot * clip_duration_us)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.64
3
+ Version: 1.0.65
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13