plexus-python-pulse-common 1.1.86__py3-none-any.whl → 1.1.88__py3-none-any.whl

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.
@@ -551,7 +551,10 @@ def model_revision_type(dialect: str | None = None) -> sa.types.TypeEngine[int]:
551
551
  raise ValueError(f"unsupported database dialect '{dialect}'")
552
552
 
553
553
 
554
- def index_condition_kwarg(dialect: str | None = None, clause: sa.TextClause | None = None) -> dict[str, sa.TextClause]:
554
+ def index_condition_kwarg(
555
+ dialect: str | None = None,
556
+ clause: sa.ClauseElement | None = None
557
+ ) -> dict[str, sa.ClauseElement | None]:
555
558
  if dialect is None:
556
559
  dialect = dialects.postgresql
557
560
  if dialect == dialects.postgresql:
@@ -728,7 +731,7 @@ def make_snapshot_model_mixin(dialect: str | None = None) -> type[SnapshotModelM
728
731
  index_name,
729
732
  "record_sqn",
730
733
  unique=True,
731
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
734
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
732
735
  )
733
736
 
734
737
  @classmethod
@@ -736,7 +739,7 @@ def make_snapshot_model_mixin(dialect: str | None = None) -> type[SnapshotModelM
736
739
  return sa.Index(
737
740
  index_name,
738
741
  *fields,
739
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
742
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
740
743
  )
741
744
 
742
745
  @classmethod
@@ -745,7 +748,7 @@ def make_snapshot_model_mixin(dialect: str | None = None) -> type[SnapshotModelM
745
748
  index_name,
746
749
  *fields,
747
750
  unique=True,
748
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
751
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
749
752
  )
750
753
 
751
754
  return ModelMixin
@@ -865,7 +868,7 @@ def make_revision_model_mixin(dialect: str | None = None) -> type[RevisionModelM
865
868
  index_name,
866
869
  "record_sqn",
867
870
  unique=True,
868
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
871
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
869
872
  )
870
873
 
871
874
  @classmethod
@@ -873,7 +876,7 @@ def make_revision_model_mixin(dialect: str | None = None) -> type[RevisionModelM
873
876
  return sa.Index(
874
877
  index_name,
875
878
  *fields,
876
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
879
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
877
880
  )
878
881
 
879
882
  @classmethod
@@ -882,7 +885,7 @@ def make_revision_model_mixin(dialect: str | None = None) -> type[RevisionModelM
882
885
  index_name,
883
886
  *fields,
884
887
  unique=True,
885
- **index_condition_kwarg(dialect, sa.text('"expired_at" IS NULL')),
888
+ **index_condition_kwarg(dialect, sa.column("expired_at").is_(None)),
886
889
  )
887
890
 
888
891
  return ModelMixin
@@ -948,6 +951,14 @@ def make_snapshot_model_trigger[SnapshotModelT: SnapshotModelMixin](engine: sa.E
948
951
  NEW."expired_at" := NULL;
949
952
  NEW."record_sqn" := nextval('{record_sqn_seq_name}');
950
953
  ELSE
954
+ -- Serialize transitions even when there is no active version.
955
+ PERFORM "sqn" FROM "{table_name}"
956
+ WHERE "record_sqn" = NEW."record_sqn"
957
+ ORDER BY "sqn" LIMIT 1 FOR UPDATE;
958
+ IF NOT FOUND THEN
959
+ RAISE EXCEPTION 'record_sqn % does not exist', NEW."record_sqn";
960
+ END IF;
961
+
951
962
  IF NEW."created_at" IS NULL THEN
952
963
  NEW."created_at" := CURRENT_TIMESTAMP;
953
964
  END IF;
@@ -1019,6 +1030,14 @@ def make_revision_model_trigger[RevisionModelT: RevisionModelMixin](engine: sa.E
1019
1030
  NEW."record_sqn" := nextval('{record_sqn_seq_name}');
1020
1031
  NEW."revision" := 1;
1021
1032
  ELSE
1033
+ -- Serialize transitions even when there is no active version.
1034
+ PERFORM "sqn" FROM "{table_name}"
1035
+ WHERE "record_sqn" = NEW."record_sqn"
1036
+ ORDER BY "sqn" LIMIT 1 FOR UPDATE;
1037
+ IF NOT FOUND THEN
1038
+ RAISE EXCEPTION 'record_sqn % does not exist', NEW."record_sqn";
1039
+ END IF;
1040
+
1022
1041
  SELECT MAX("created_at") INTO NEW."created_at"
1023
1042
  FROM "{table_name}"
1024
1043
  WHERE "record_sqn" = NEW."record_sqn";
@@ -1070,6 +1089,7 @@ def clone_sequence_model_instance[SequenceModelT: SequenceModelMixin](
1070
1089
  return instance
1071
1090
  if clear_meta_fields:
1072
1091
  result.sqn = None
1092
+ result.model_fields_set.discard("sqn")
1073
1093
  return result
1074
1094
 
1075
1095
 
@@ -1087,6 +1107,9 @@ def clone_changing_model_instance[ChangingModelT: ChangingModelMixin](
1087
1107
  result.sqn = None
1088
1108
  result.created_at = None
1089
1109
  result.updated_at = None
1110
+ result.model_fields_set.discard("sqn")
1111
+ result.model_fields_set.discard("created_at")
1112
+ result.model_fields_set.discard("updated_at")
1090
1113
  return result
1091
1114
 
1092
1115
 
@@ -1105,6 +1128,10 @@ def clone_snapshot_model_instance[SnapshotModelT: SnapshotModelMixin](
1105
1128
  result.created_at = None
1106
1129
  result.expired_at = None
1107
1130
  result.record_sqn = None
1131
+ result.model_fields_set.discard("sqn")
1132
+ result.model_fields_set.discard("created_at")
1133
+ result.model_fields_set.discard("expired_at")
1134
+ result.model_fields_set.discard("record_sqn")
1108
1135
  return result
1109
1136
 
1110
1137
 
@@ -1125,6 +1152,12 @@ def clone_revision_model_instance[RevisionModelT: RevisionModelMixin](
1125
1152
  result.expired_at = None
1126
1153
  result.record_sqn = None
1127
1154
  result.revision = None
1155
+ result.model_fields_set.discard("sqn")
1156
+ result.model_fields_set.discard("created_at")
1157
+ result.model_fields_set.discard("updated_at")
1158
+ result.model_fields_set.discard("expired_at")
1159
+ result.model_fields_set.discard("record_sqn")
1160
+ result.model_fields_set.discard("revision")
1128
1161
  return result
1129
1162
 
1130
1163
 
@@ -1144,6 +1177,41 @@ def db_make_order_by_clause[SequenceModelT: SequenceModelMixin](
1144
1177
  return order_criteria
1145
1178
 
1146
1179
 
1180
+ def db_lock_model_record[ModelT: RevisionModelMixin | SnapshotModelMixin](
1181
+ db: sa_orm.Session,
1182
+ model: type[ModelT],
1183
+ record_sqn: int,
1184
+ ) -> None:
1185
+ """
1186
+ Lock the first history row, including when the record is inactive.
1187
+
1188
+ Select by ``record_sqn`` rather than assuming it equals the first ``sqn``: trigger-managed
1189
+ records use a separate sequence. Every lifecycle writer must acquire this same lock before
1190
+ reading current state.
1191
+ Locks last until caller commit or rollback. Do not delete the first history row.
1192
+ """
1193
+ db_anchor = (
1194
+ db
1195
+ .query(model)
1196
+ .where(model.record_sqn == record_sqn)
1197
+ .order_by(model.sqn)
1198
+ .limit(1)
1199
+ .with_for_update()
1200
+ .populate_existing()
1201
+ .one_or_none()
1202
+ )
1203
+ if db_anchor is None:
1204
+ raise sa_exc.NoResultFound(f"'{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1205
+
1206
+
1207
+ # Mutating helpers flush but never begin, commit, or roll back transactions. Callers must roll
1208
+ # back on failure. Row locks serialize cooperating writers on databases supporting SELECT FOR UPDATE;
1209
+ # SQLite callers must serialize writes themselves (e.g. begin an IMMEDIATE transaction before
1210
+ # reading).
1211
+ # At stronger isolation levels, callers must also handle serialization failures by retrying
1212
+ # transactions. History rows used as lock anchors must not be deleted while a record exists.
1213
+
1214
+
1147
1215
  def db_create_sequence_model[SequenceModelT: SequenceModelMixin](
1148
1216
  db: sa_orm.Session,
1149
1217
  model: type[SequenceModelT],
@@ -1201,11 +1269,11 @@ def db_update_sequence_model[SequenceModelT: SequenceModelMixin](
1201
1269
  instance: SequenceModelMixin,
1202
1270
  sqn: int,
1203
1271
  ) -> SequenceModelT:
1204
- db_instance = db.query(model).where(model.sqn == sqn).one_or_none()
1272
+ db_instance = db.query(model).where(model.sqn == sqn).with_for_update().populate_existing().one_or_none()
1205
1273
  if db_instance is None:
1206
1274
  raise sa_exc.NoResultFound(f"'{model_name_of(model)}' of specified sqn '{sqn}' not found")
1207
1275
 
1208
- db_instance = model_copy_from(db_instance, clone_sequence_model_instance(model, instance), exclude_none=True)
1276
+ db_instance = model_copy_from(db_instance, clone_sequence_model_instance(model, instance), exclude_unset=True)
1209
1277
  clone_sequence_model_instance(model, db_instance, validate_only=True)
1210
1278
  db.flush()
1211
1279
 
@@ -1217,7 +1285,7 @@ def db_delete_sequence_model[SequenceModelT: SequenceModelMixin](
1217
1285
  model: type[SequenceModelT],
1218
1286
  sqn: int,
1219
1287
  ) -> SequenceModelT:
1220
- db_instance = db.query(model).where(model.sqn == sqn).one_or_none()
1288
+ db_instance = db.query(model).where(model.sqn == sqn).with_for_update().populate_existing().one_or_none()
1221
1289
  if db_instance is None:
1222
1290
  raise sa_exc.NoResultFound(f"'{model_name_of(model)}' of specified sqn '{sqn}' not found")
1223
1291
 
@@ -1265,11 +1333,11 @@ def db_update_changing_model[ChangingModelT: ChangingModelMixin](
1265
1333
  sqn: int,
1266
1334
  updated_at: datetime.datetime,
1267
1335
  ) -> ChangingModelT:
1268
- db_instance = db.query(model).where(model.sqn == sqn).one_or_none()
1336
+ db_instance = db.query(model).where(model.sqn == sqn).with_for_update().populate_existing().one_or_none()
1269
1337
  if db_instance is None:
1270
1338
  raise sa_exc.NoResultFound(f"'{model_name_of(model)}' of specified sqn '{sqn}' not found")
1271
1339
 
1272
- db_instance = model_copy_from(db_instance, clone_changing_model_instance(model, instance), exclude_none=True)
1340
+ db_instance = model_copy_from(db_instance, clone_changing_model_instance(model, instance), exclude_unset=True)
1273
1341
  db_instance.updated_at = updated_at
1274
1342
  clone_changing_model_instance(model, db_instance, validate_only=True)
1275
1343
  db.flush()
@@ -1425,7 +1493,14 @@ def db_update_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1425
1493
  record_sqn: int,
1426
1494
  updated_at: datetime.datetime,
1427
1495
  ) -> SnapshotModelT:
1428
- db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1496
+ db_lock_model_record(db, model, record_sqn)
1497
+ db_instance = (
1498
+ db
1499
+ .query(model)
1500
+ .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1501
+ .populate_existing()
1502
+ .one_or_none()
1503
+ )
1429
1504
  if db_instance is None:
1430
1505
  raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1431
1506
 
@@ -1450,10 +1525,12 @@ def db_expire_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1450
1525
  record_sqn: int,
1451
1526
  updated_at: datetime.datetime,
1452
1527
  ) -> SnapshotModelT:
1528
+ db_lock_model_record(db, model, record_sqn)
1453
1529
  db_instance = (
1454
1530
  db
1455
1531
  .query(model)
1456
1532
  .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1533
+ .populate_existing()
1457
1534
  .one_or_none()
1458
1535
  )
1459
1536
  if db_instance is None:
@@ -1472,16 +1549,24 @@ def db_activate_snapshot_model[SnapshotModelT: SnapshotModelMixin](
1472
1549
  record_sqn: int,
1473
1550
  updated_at: datetime.datetime,
1474
1551
  ) -> SnapshotModelT:
1475
- db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1552
+ db_lock_model_record(db, model, record_sqn)
1553
+ db_instance = (
1554
+ db
1555
+ .query(model)
1556
+ .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1557
+ .populate_existing()
1558
+ .one_or_none()
1559
+ )
1476
1560
  if db_instance is not None:
1477
1561
  raise sa_exc.MultipleResultsFound(
1478
- f"Active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' already exists")
1562
+ f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' already exists")
1479
1563
 
1480
1564
  db_instance = (
1481
1565
  db
1482
1566
  .query(model)
1483
1567
  .where(model.record_sqn == record_sqn, model.expired_at.is_not(None))
1484
- .order_by(model.created_at.desc())
1568
+ .order_by(model.created_at.desc(), model.sqn.desc())
1569
+ .populate_existing()
1485
1570
  .first()
1486
1571
  )
1487
1572
  if db_instance is None:
@@ -1653,7 +1738,14 @@ def db_update_revision_model[RevisionModelT: RevisionModelMixin](
1653
1738
  record_sqn: int,
1654
1739
  updated_at: datetime.datetime,
1655
1740
  ) -> RevisionModelT:
1656
- db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1741
+ db_lock_model_record(db, model, record_sqn)
1742
+ db_instance = (
1743
+ db
1744
+ .query(model)
1745
+ .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1746
+ .populate_existing()
1747
+ .one_or_none()
1748
+ )
1657
1749
  if db_instance is None:
1658
1750
  raise sa_exc.NoResultFound(f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' not found")
1659
1751
 
@@ -1680,10 +1772,12 @@ def db_expire_revision_model[RevisionModelT: RevisionModelMixin](
1680
1772
  record_sqn: int,
1681
1773
  updated_at: datetime.datetime,
1682
1774
  ) -> RevisionModelT:
1775
+ db_lock_model_record(db, model, record_sqn)
1683
1776
  db_instance = (
1684
1777
  db
1685
1778
  .query(model)
1686
1779
  .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1780
+ .populate_existing()
1687
1781
  .one_or_none()
1688
1782
  )
1689
1783
  if db_instance is None:
@@ -1702,7 +1796,14 @@ def db_activate_revision_model[RevisionModelT: RevisionModelMixin](
1702
1796
  record_sqn: int,
1703
1797
  updated_at: datetime.datetime,
1704
1798
  ) -> RevisionModelT:
1705
- db_instance = db.query(model).where(model.record_sqn == record_sqn, model.expired_at.is_(None)).one_or_none()
1799
+ db_lock_model_record(db, model, record_sqn)
1800
+ db_instance = (
1801
+ db
1802
+ .query(model)
1803
+ .where(model.record_sqn == record_sqn, model.expired_at.is_(None))
1804
+ .populate_existing()
1805
+ .one_or_none()
1806
+ )
1706
1807
  if db_instance is not None:
1707
1808
  raise sa_exc.MultipleResultsFound(
1708
1809
  f"active '{model_name_of(model)}' of specified record_sqn '{record_sqn}' already exists")
@@ -1712,6 +1813,7 @@ def db_activate_revision_model[RevisionModelT: RevisionModelMixin](
1712
1813
  .query(model)
1713
1814
  .where(model.record_sqn == record_sqn, model.expired_at.is_not(None))
1714
1815
  .order_by(model.revision.desc())
1816
+ .populate_existing()
1715
1817
  .first()
1716
1818
  )
1717
1819
  if db_instance is None:
@@ -28,6 +28,8 @@ __all__ = [
28
28
  "strict_fragmented_abspath_parser",
29
29
  "email_address_pattern",
30
30
  "email_address_parser",
31
+ "numeric_version_pattern",
32
+ "numeric_version_parser",
31
33
  "semver_pattern",
32
34
  "semver_parser",
33
35
  "colon_tag_pattern",
@@ -91,6 +93,7 @@ sharp_token: pp.ParserElement = pp.Char("#")
91
93
  lower_regexp: re.Pattern[str] = re.compile(r"[a-z]")
92
94
  upper_regexp: re.Pattern[str] = re.compile(r"[A-Z]")
93
95
  digit_regexp: re.Pattern[str] = re.compile(r"[0-9]")
96
+ alpha_regexp: re.Pattern[str] = re.compile(r"[a-zA-Z]")
94
97
  lower_digit_regexp: re.Pattern[str] = re.compile(r"[a-z0-9]")
95
98
  upper_digit_regexp: re.Pattern[str] = re.compile(r"[A-Z0-9]")
96
99
  alpha_digit_regexp: re.Pattern[str] = re.compile(r"[a-zA-Z0-9]")
@@ -99,6 +102,7 @@ hex_digit_regexp: re.Pattern[str] = re.compile(r"[a-f0-9]")
99
102
  lowers_regexp: re.Pattern[str] = re.compile(rf"{lower_regexp.pattern}+")
100
103
  uppers_regexp: re.Pattern[str] = re.compile(rf"{upper_regexp.pattern}+")
101
104
  digits_regexp: re.Pattern[str] = re.compile(rf"{digit_regexp.pattern}+")
105
+ alphas_regexp: re.Pattern[str] = re.compile(rf"{alpha_regexp.pattern}+")
102
106
  lower_digits_regexp: re.Pattern[str] = re.compile(rf"{lower_digit_regexp.pattern}+")
103
107
  upper_digits_regexp: re.Pattern[str] = re.compile(rf"{upper_digit_regexp.pattern}+")
104
108
  alpha_digits_regexp: re.Pattern[str] = re.compile(rf"{alpha_digit_regexp.pattern}+")
@@ -110,6 +114,7 @@ strict_chars_regexp: re.Pattern[str] = re.compile(rf"({alpha_digit_regexp.patter
110
114
  lower_element: pp.ParserElement = pp.Regex(lower_regexp.pattern)
111
115
  upper_element: pp.ParserElement = pp.Regex(upper_regexp.pattern)
112
116
  digit_element: pp.ParserElement = pp.Regex(digit_regexp.pattern)
117
+ alpha_element: pp.ParserElement = pp.Regex(alpha_regexp.pattern)
113
118
  lower_digit_element: pp.ParserElement = pp.Regex(lower_digit_regexp.pattern)
114
119
  upper_digit_element: pp.ParserElement = pp.Regex(upper_digit_regexp.pattern)
115
120
  alpha_digit_element: pp.ParserElement = pp.Regex(alpha_digit_regexp.pattern)
@@ -118,6 +123,7 @@ hex_digit_element: pp.ParserElement = pp.Regex(hex_digit_regexp.pattern)
118
123
  lowers_element: pp.ParserElement = pp.Regex(lowers_regexp.pattern)
119
124
  uppers_element: pp.ParserElement = pp.Regex(uppers_regexp.pattern)
120
125
  digits_element: pp.ParserElement = pp.Regex(digits_regexp.pattern)
126
+ alphas_element: pp.ParserElement = pp.Regex(alphas_regexp.pattern)
121
127
  lower_digits_element: pp.ParserElement = pp.Regex(lower_digits_regexp.pattern)
122
128
  upper_digits_element: pp.ParserElement = pp.Regex(upper_digits_regexp.pattern)
123
129
  alpha_digits_element: pp.ParserElement = pp.Regex(alpha_digits_regexp.pattern)
@@ -184,10 +190,14 @@ email_address_regexp: re.Pattern[str] = re.compile(
184
190
  rf"(({lower_digit_regexp.pattern}|[_-])+)(?:\.({lower_digit_regexp.pattern}|[_-])+)*@(?:{kebab_case_regexp.pattern}\.)+({lower_digit_regexp.pattern}{{2,63}})")
185
191
  email_address_element: pp.ParserElement = pp.Regex(email_address_regexp.pattern)
186
192
 
193
+ numeric_version_regexp: re.Pattern[str] = re.compile(
194
+ rf"(?:{number_regexp.pattern})(?:\.(?:{number_regexp.pattern}))*")
195
+ numeric_version_element: pp.ParserElement = pp.Regex(numeric_version_regexp.pattern)
196
+
187
197
  semver_regexp: re.Pattern[str] = re.compile(
188
- rf"({number_regexp.pattern})\.({number_regexp.pattern})\.({number_regexp.pattern})"
189
- rf"(?:-{alpha_digits_regexp.pattern}(?:\.{alpha_digits_regexp.pattern})*)?"
190
- rf"(?:\+{alpha_digits_regexp.pattern}(?:\.{alpha_digits_regexp.pattern})*)?")
198
+ rf"(?:{number_regexp.pattern})\.(?:{number_regexp.pattern})\.(?:{number_regexp.pattern})"
199
+ rf"(?:-(?:(?:{alpha_digit_regexp.pattern}|-)*(?:{alpha_regexp.pattern}|-)(?:{alpha_digit_regexp.pattern}|-)*|{number_regexp.pattern})(?:\.(?:(?:{alpha_digit_regexp.pattern}|-)*(?:{alpha_regexp.pattern}|-)(?:{alpha_digit_regexp.pattern}|-)*|{number_regexp.pattern}))*)?"
200
+ rf"(?:\+(?:{alpha_digit_regexp.pattern}|-)+(?:\.(?:{alpha_digit_regexp.pattern}|-)+)*)?")
191
201
  semver_element: pp.ParserElement = pp.Regex(semver_regexp.pattern)
192
202
 
193
203
  colon_tag_regexp: re.Pattern[str] = re.compile(rf"{snake_case_regexp.pattern}(?::{snake_case_regexp.pattern})*")
@@ -229,6 +239,9 @@ strict_fragmented_abspath_parser = make_string_parser(strict_fragmented_abspath_
229
239
  email_address_pattern = make_string_pattern(email_address_regexp)
230
240
  email_address_parser = make_string_parser(email_address_element)
231
241
 
242
+ numeric_version_pattern = make_string_pattern(numeric_version_regexp)
243
+ numeric_version_parser = make_string_parser(numeric_version_element)
244
+
232
245
  semver_pattern = make_string_pattern(semver_regexp)
233
246
  semver_parser = make_string_parser(semver_element)
234
247
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-pulse-common
3
- Version: 1.1.86
3
+ Version: 1.1.88
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -17,14 +17,14 @@ plexus/pulse/common/utils/dockerutils.py,sha256=7DF9yFGz8uHwpYk115xBWqOjs5etz0UE
17
17
  plexus/pulse/common/utils/flowutils.py,sha256=MGu15KL9mw-LyGGLceVmY-c2FGer5PURnMZWFSJN--I,10182
18
18
  plexus/pulse/common/utils/gisutils.py,sha256=Cx0ZB9-NA_7VuzbuzRArTJ5UfltG-KmvVC8YxPIgPE4,11600
19
19
  plexus/pulse/common/utils/jsonutils.py,sha256=zSbW94klN2jrkMdBECu4RNJX6zzNaSxauQ3FuihuB6E,3342
20
- plexus/pulse/common/utils/ormutils.py,sha256=FPkkdmiTZ1M0ohLTQakA8shO1MD_2mhM6NolpnD4OWo,64472
20
+ plexus/pulse/common/utils/ormutils.py,sha256=taaZzpIH0C-ShEmOWf-vt22bgcFFqH1FnJlNcH9tEFM,68222
21
21
  plexus/pulse/common/utils/pathutils.py,sha256=eYfNK48wf1xZGK2IbpKxYOn4skZhWWA8OTyUwDXGMIc,9222
22
22
  plexus/pulse/common/utils/s3utils.py,sha256=5q5wSOavt-n5EdrflQX7WPGgDeWQkGO9lG5Nq3QXjCk,35904
23
23
  plexus/pulse/common/utils/sqlutils.py,sha256=D6kTBjhO5YlNRt3uFlPt6z3uH61m9ajEzPYmsI6NoFc,231
24
- plexus/pulse/common/utils/strutils.py,sha256=PkmMHCcHN9vALCy_8fJTf00Dg7I2N4QTCXdBmB3t7rk,16440
24
+ plexus/pulse/common/utils/strutils.py,sha256=8kl88-u89FApoaq-D2ulk5DNl9kenP6vqksvVJ0iH4s,17325
25
25
  plexus/pulse/common/utils/tagutils.py,sha256=V8eUxtWSijX6nHSCuRX2dTJz3why3TCOzPVK5YXt28s,66944
26
26
  plexus/pulse/common/utils/testutils.py,sha256=hKPRvoN4mg8QGQJH121l59a0uo8jN5rntGQAVYplGBw,6166
27
- plexus_python_pulse_common-1.1.86.dist-info/METADATA,sha256=iUzRtV5lw3poGoHcPb34GNzXZpMNynfVAQYT1cRpI5w,1555
28
- plexus_python_pulse_common-1.1.86.dist-info/WHEEL,sha256=a-udL2m4V_6Yn8b-uKzWb12RjRxwwMo6ohYFM24tpaY,91
29
- plexus_python_pulse_common-1.1.86.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
30
- plexus_python_pulse_common-1.1.86.dist-info/RECORD,,
27
+ plexus_python_pulse_common-1.1.88.dist-info/METADATA,sha256=bNGz7B5jj23hayiYBRvtfRpfnsD21flHjbg-sgdiKMY,1555
28
+ plexus_python_pulse_common-1.1.88.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
29
+ plexus_python_pulse_common-1.1.88.dist-info/top_level.txt,sha256=ug_g7CVwaMQuas5UzAXbHUrQvKGCn8ezc6ZNvvRlJOE,7
30
+ plexus_python_pulse_common-1.1.88.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.2.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5