plexus-python-pulse-common 1.1.87__tar.gz → 1.1.88__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/PKG-INFO +1 -1
  2. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/ormutils.py +10 -7
  3. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/strutils.py +16 -3
  4. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/PKG-INFO +1 -1
  5. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/strutils_test.py +177 -71
  6. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/.editorconfig +0 -0
  7. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/.github/workflows/pr.yml +0 -0
  8. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/.github/workflows/push.yml +0 -0
  9. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/.gitignore +0 -0
  10. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/MANIFEST.in +0 -0
  11. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/README.md +0 -0
  12. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/VERSION +0 -0
  13. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/pyproject.toml +0 -0
  14. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  15. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  16. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  17. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/0-dummy +0 -0
  18. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/1-dummy +0 -0
  19. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/2-dummy +0 -0
  20. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.0.0.jsonl +0 -0
  21. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.0.0.vol-0.jsonl +0 -0
  22. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.0.jsonl +0 -0
  23. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.1.1.jsonl +0 -0
  24. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.1.1.vol-1.jsonl +0 -0
  25. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.1.jsonl +0 -0
  26. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.2.2.jsonl +0 -0
  27. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.2.2.vol-2.jsonl +0 -0
  28. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.2.jsonl +0 -0
  29. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.csv.part0 +0 -0
  30. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.csv.part1 +0 -0
  31. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.csv.part2 +0 -0
  32. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/pathutils/dummy.txt +0 -0
  33. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  34. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  35. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  36. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  37. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  38. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  39. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  40. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  41. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  42. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  43. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils_archive/archive.compressed.zip +0 -0
  44. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/resources/unittest/s3utils_archive/archive.uncompressed.zip +0 -0
  45. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/setup.cfg +0 -0
  46. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/setup.py +0 -0
  47. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/__init__.py +0 -0
  48. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/carto/OSMFile.py +0 -0
  49. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/carto/OSMNode.py +0 -0
  50. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/carto/OSMTags.py +0 -0
  51. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/carto/OSMWay.py +0 -0
  52. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/carto/__init__.py +0 -0
  53. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/resources/__init__.py +0 -0
  54. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/resources/tags/__init__.py +0 -0
  55. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/resources/tags/unittest-1.0.0.tagset.yaml +0 -0
  56. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/resources/tags/universal-1.0.0.tagset.yaml +0 -0
  57. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/__init__.py +0 -0
  58. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/apiutils.py +0 -0
  59. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/bagutils.py +0 -0
  60. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/config.py +0 -0
  61. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/datautils.py +0 -0
  62. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/dockerutils.py +0 -0
  63. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/flowutils.py +0 -0
  64. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/gisutils.py +0 -0
  65. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/jsonutils.py +0 -0
  66. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/pathutils.py +0 -0
  67. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/s3utils.py +0 -0
  68. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/sqlutils.py +0 -0
  69. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/tagutils.py +0 -0
  70. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus/pulse/common/utils/testutils.py +0 -0
  71. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/SOURCES.txt +0 -0
  72. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/dependency_links.txt +0 -0
  73. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/not-zip-safe +0 -0
  74. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/requires.txt +0 -0
  75. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/src/plexus_python_pulse_common.egg-info/top_level.txt +0 -0
  76. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/__init__.py +0 -0
  77. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/__init__.py +0 -0
  78. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/__init__.py +0 -0
  79. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/carto/__init__.py +0 -0
  80. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/carto/osm_file_test.py +0 -0
  81. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/carto/osm_tags_test.py +0 -0
  82. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/__init__.py +0 -0
  83. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/apiutils_test.py +0 -0
  84. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/bagutils_test.py +0 -0
  85. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/datautils_test.py +0 -0
  86. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/dockerutils_test.py +0 -0
  87. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/flowutils_test.py +0 -0
  88. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/gisutils_test.py +0 -0
  89. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/jsonutils_test.py +0 -0
  90. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/ormutils_test.py +0 -0
  91. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/pathutils_test.py +0 -0
  92. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/s3utils_test.py +0 -0
  93. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/tagutils_test.py +0 -0
  94. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/plexus_tests/pulse/common/utils/testutils_test.py +0 -0
  95. {plexus_python_pulse_common-1.1.87 → plexus_python_pulse_common-1.1.88}/test/testenv.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-pulse-common
3
- Version: 1.1.87
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
@@ -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
@@ -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.87
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
@@ -10,6 +10,7 @@ from plexus.pulse.common.utils.strutils import dot_case_parser, dot_case_pattern
10
10
  from plexus.pulse.common.utils.strutils import email_address_parser, email_address_pattern
11
11
  from plexus.pulse.common.utils.strutils import hex_string_parser, hex_string_pattern
12
12
  from plexus.pulse.common.utils.strutils import kebab_case_parser, kebab_case_pattern
13
+ from plexus.pulse.common.utils.strutils import numeric_version_parser, numeric_version_pattern
13
14
  from plexus.pulse.common.utils.strutils import parse_bag_name, parse_user_email, parse_user_name, parse_vehicle_name
14
15
  from plexus.pulse.common.utils.strutils import semver_parser, semver_pattern
15
16
  from plexus.pulse.common.utils.strutils import snake_case_parser, snake_case_pattern
@@ -32,8 +33,10 @@ class StrUtilsTest(unittest.TestCase):
32
33
  @ddt.idata(data_hex_string_pattern)
33
34
  @ddt.unpack
34
35
  def test_hex_string_pattern(self, data):
35
- self.assertIsNotNone(hex_string_pattern.match(data))
36
- self.assertIsNotNone(hex_string_parser.parse_string(data, parse_all=True))
36
+ with self.subTest(checker="regex"):
37
+ self.assertIsNotNone(hex_string_pattern.fullmatch(data))
38
+ with self.subTest(checker="parser"):
39
+ self.assertIsNotNone(hex_string_parser.parse_string(data, parse_all=True))
37
40
 
38
41
  data_hex_string_pattern__bad_cases = [
39
42
  ("",),
@@ -48,9 +51,11 @@ class StrUtilsTest(unittest.TestCase):
48
51
  @ddt.idata(data_hex_string_pattern__bad_cases)
49
52
  @ddt.unpack
50
53
  def test_hex_string_pattern__bad_cases(self, data):
51
- self.assertIsNone(hex_string_pattern.match(data))
52
- with self.assertRaises(pp.ParseException):
53
- hex_string_parser.parse_string(data, parse_all=True)
54
+ with self.subTest(checker="regex"):
55
+ self.assertIsNone(hex_string_pattern.fullmatch(data))
56
+ with self.subTest(checker="parser"):
57
+ with self.assertRaises(pp.ParseException):
58
+ hex_string_parser.parse_string(data, parse_all=True)
54
59
 
55
60
  data_snake_case_pattern = [
56
61
  ("d",),
@@ -75,8 +80,10 @@ class StrUtilsTest(unittest.TestCase):
75
80
  @ddt.idata(data_snake_case_pattern)
76
81
  @ddt.unpack
77
82
  def test_snake_case_pattern(self, data):
78
- self.assertIsNotNone(snake_case_pattern.match(data))
79
- self.assertIsNotNone(snake_case_parser.parse_string(data, parse_all=True))
83
+ with self.subTest(checker="regex"):
84
+ self.assertIsNotNone(snake_case_pattern.fullmatch(data))
85
+ with self.subTest(checker="parser"):
86
+ self.assertIsNotNone(snake_case_parser.parse_string(data, parse_all=True))
80
87
 
81
88
  data_snake_case_pattern__bad_cases = [
82
89
  ("",),
@@ -102,9 +109,11 @@ class StrUtilsTest(unittest.TestCase):
102
109
  @ddt.idata(data_snake_case_pattern__bad_cases)
103
110
  @ddt.unpack
104
111
  def test_snake_case_pattern__bad_cases(self, data):
105
- self.assertIsNone(snake_case_pattern.match(data))
106
- with self.assertRaises(pp.ParseException):
107
- snake_case_parser.parse_string(data, parse_all=True)
112
+ with self.subTest(checker="regex"):
113
+ self.assertIsNone(snake_case_pattern.fullmatch(data))
114
+ with self.subTest(checker="parser"):
115
+ with self.assertRaises(pp.ParseException):
116
+ snake_case_parser.parse_string(data, parse_all=True)
108
117
 
109
118
  data_kebab_case_pattern = [
110
119
  ("d",),
@@ -129,8 +138,10 @@ class StrUtilsTest(unittest.TestCase):
129
138
  @ddt.idata(data_kebab_case_pattern)
130
139
  @ddt.unpack
131
140
  def test_kebab_case_pattern(self, data):
132
- self.assertIsNotNone(kebab_case_pattern.match(data))
133
- self.assertIsNotNone(kebab_case_parser.parse_string(data, parse_all=True))
141
+ with self.subTest(checker="regex"):
142
+ self.assertIsNotNone(kebab_case_pattern.fullmatch(data))
143
+ with self.subTest(checker="parser"):
144
+ self.assertIsNotNone(kebab_case_parser.parse_string(data, parse_all=True))
134
145
 
135
146
  data_kebab_case_pattern__bad_cases = [
136
147
  ("",),
@@ -156,9 +167,11 @@ class StrUtilsTest(unittest.TestCase):
156
167
  @ddt.idata(data_kebab_case_pattern__bad_cases)
157
168
  @ddt.unpack
158
169
  def test_kebab_case_pattern__bad_cases(self, data):
159
- self.assertIsNone(kebab_case_pattern.match(data))
160
- with self.assertRaises(pp.ParseException):
161
- kebab_case_parser.parse_string(data, parse_all=True)
170
+ with self.subTest(checker="regex"):
171
+ self.assertIsNone(kebab_case_pattern.fullmatch(data))
172
+ with self.subTest(checker="parser"):
173
+ with self.assertRaises(pp.ParseException):
174
+ kebab_case_parser.parse_string(data, parse_all=True)
162
175
 
163
176
  data_dot_case_pattern = [
164
177
  ("d",),
@@ -183,8 +196,10 @@ class StrUtilsTest(unittest.TestCase):
183
196
  @ddt.idata(data_dot_case_pattern)
184
197
  @ddt.unpack
185
198
  def test_dot_case_pattern(self, data):
186
- self.assertIsNotNone(dot_case_pattern.match(data))
187
- self.assertIsNotNone(dot_case_parser.parse_string(data, parse_all=True))
199
+ with self.subTest(checker="regex"):
200
+ self.assertIsNotNone(dot_case_pattern.fullmatch(data))
201
+ with self.subTest(checker="parser"):
202
+ self.assertIsNotNone(dot_case_parser.parse_string(data, parse_all=True))
188
203
 
189
204
  data_dot_case_pattern__bad_cases = [
190
205
  ("",),
@@ -210,9 +225,11 @@ class StrUtilsTest(unittest.TestCase):
210
225
  @ddt.idata(data_dot_case_pattern__bad_cases)
211
226
  @ddt.unpack
212
227
  def test_dot_case_pattern__bad_cases(self, data):
213
- self.assertIsNone(dot_case_pattern.match(data))
214
- with self.assertRaises(pp.ParseException):
215
- dot_case_parser.parse_string(data, parse_all=True)
228
+ with self.subTest(checker="regex"):
229
+ self.assertIsNone(dot_case_pattern.fullmatch(data))
230
+ with self.subTest(checker="parser"):
231
+ with self.assertRaises(pp.ParseException):
232
+ dot_case_parser.parse_string(data, parse_all=True)
216
233
 
217
234
  data_strict_relpath_pattern = [
218
235
  ("",),
@@ -239,8 +256,10 @@ class StrUtilsTest(unittest.TestCase):
239
256
  @ddt.idata(data_strict_relpath_pattern)
240
257
  @ddt.unpack
241
258
  def test_strict_relpath_pattern(self, data):
242
- self.assertIsNotNone(strict_relpath_pattern.match(data))
243
- self.assertIsNotNone(strict_relpath_parser.parse_string(data, parse_all=True))
259
+ with self.subTest(checker="regex"):
260
+ self.assertIsNotNone(strict_relpath_pattern.fullmatch(data))
261
+ with self.subTest(checker="parser"):
262
+ self.assertIsNotNone(strict_relpath_parser.parse_string(data, parse_all=True))
244
263
 
245
264
  data_strict_relpath_pattern__bad_cases = [
246
265
  ("/",),
@@ -293,9 +312,11 @@ class StrUtilsTest(unittest.TestCase):
293
312
  @ddt.idata(data_strict_relpath_pattern__bad_cases)
294
313
  @ddt.unpack
295
314
  def test_strict_relpath_pattern__bad_cases(self, data):
296
- self.assertIsNone(strict_relpath_pattern.match(data))
297
- with self.assertRaises(pp.ParseException):
298
- strict_relpath_parser.parse_string(data, parse_all=True)
315
+ with self.subTest(checker="regex"):
316
+ self.assertIsNone(strict_relpath_pattern.fullmatch(data))
317
+ with self.subTest(checker="parser"):
318
+ with self.assertRaises(pp.ParseException):
319
+ strict_relpath_parser.parse_string(data, parse_all=True)
299
320
 
300
321
  data_strict_abspath_pattern = [
301
322
  ("/",),
@@ -322,8 +343,10 @@ class StrUtilsTest(unittest.TestCase):
322
343
  @ddt.idata(data_strict_abspath_pattern)
323
344
  @ddt.unpack
324
345
  def test_strict_abspath_pattern(self, data):
325
- self.assertIsNotNone(strict_abspath_pattern.match(data))
326
- self.assertIsNotNone(strict_abspath_parser.parse_string(data, parse_all=True))
346
+ with self.subTest(checker="regex"):
347
+ self.assertIsNotNone(strict_abspath_pattern.fullmatch(data))
348
+ with self.subTest(checker="parser"):
349
+ self.assertIsNotNone(strict_abspath_parser.parse_string(data, parse_all=True))
327
350
 
328
351
  data_strict_abspath_pattern__bad_cases = [
329
352
  ("",),
@@ -376,9 +399,11 @@ class StrUtilsTest(unittest.TestCase):
376
399
  @ddt.idata(data_strict_abspath_pattern__bad_cases)
377
400
  @ddt.unpack
378
401
  def test_strict_abspath_pattern__bad_cases(self, data):
379
- self.assertIsNone(strict_abspath_pattern.match(data))
380
- with self.assertRaises(pp.ParseException):
381
- strict_abspath_parser.parse_string(data, parse_all=True)
402
+ with self.subTest(checker="regex"):
403
+ self.assertIsNone(strict_abspath_pattern.fullmatch(data))
404
+ with self.subTest(checker="parser"):
405
+ with self.assertRaises(pp.ParseException):
406
+ strict_abspath_parser.parse_string(data, parse_all=True)
382
407
 
383
408
  data_strict_fragmented_relpath_pattern = [
384
409
  ("archive.zip",),
@@ -403,8 +428,10 @@ class StrUtilsTest(unittest.TestCase):
403
428
  @ddt.idata(data_strict_fragmented_relpath_pattern)
404
429
  @ddt.unpack
405
430
  def test_strict_fragmented_relpath_pattern(self, data):
406
- self.assertIsNotNone(strict_fragmented_relpath_pattern.match(data))
407
- self.assertIsNotNone(strict_fragmented_relpath_parser.parse_string(data, parse_all=True))
431
+ with self.subTest(checker="regex"):
432
+ self.assertIsNotNone(strict_fragmented_relpath_pattern.fullmatch(data))
433
+ with self.subTest(checker="parser"):
434
+ self.assertIsNotNone(strict_fragmented_relpath_parser.parse_string(data, parse_all=True))
408
435
 
409
436
  data_strict_fragmented_relpath_pattern__bad_cases = [
410
437
  ("/archive.zip",),
@@ -450,9 +477,11 @@ class StrUtilsTest(unittest.TestCase):
450
477
  @ddt.idata(data_strict_fragmented_relpath_pattern__bad_cases)
451
478
  @ddt.unpack
452
479
  def test_strict_fragmented_relpath_pattern__bad_cases(self, data):
453
- self.assertIsNone(strict_fragmented_relpath_pattern.match(data))
454
- with self.assertRaises(pp.ParseException):
455
- strict_fragmented_relpath_parser.parse_string(data, parse_all=True)
480
+ with self.subTest(checker="regex"):
481
+ self.assertIsNone(strict_fragmented_relpath_pattern.fullmatch(data))
482
+ with self.subTest(checker="parser"):
483
+ with self.assertRaises(pp.ParseException):
484
+ strict_fragmented_relpath_parser.parse_string(data, parse_all=True)
456
485
 
457
486
  data_strict_fragmented_abspath_pattern = [
458
487
  ("/archive.zip",),
@@ -477,8 +506,10 @@ class StrUtilsTest(unittest.TestCase):
477
506
  @ddt.idata(data_strict_fragmented_abspath_pattern)
478
507
  @ddt.unpack
479
508
  def test_strict_fragmented_abspath_pattern(self, data):
480
- self.assertIsNotNone(strict_fragmented_abspath_pattern.match(data))
481
- self.assertIsNotNone(strict_fragmented_abspath_parser.parse_string(data, parse_all=True))
509
+ with self.subTest(checker="regex"):
510
+ self.assertIsNotNone(strict_fragmented_abspath_pattern.fullmatch(data))
511
+ with self.subTest(checker="parser"):
512
+ self.assertIsNotNone(strict_fragmented_abspath_parser.parse_string(data, parse_all=True))
482
513
 
483
514
  data_strict_fragmented_abspath_pattern__bad_cases = [
484
515
  ("archive.zip",),
@@ -524,9 +555,11 @@ class StrUtilsTest(unittest.TestCase):
524
555
  @ddt.idata(data_strict_fragmented_abspath_pattern__bad_cases)
525
556
  @ddt.unpack
526
557
  def test_strict_fragmented_abspath_pattern__bad_cases(self, data):
527
- self.assertIsNone(strict_fragmented_abspath_pattern.match(data))
528
- with self.assertRaises(pp.ParseException):
529
- strict_fragmented_abspath_parser.parse_string(data, parse_all=True)
558
+ with self.subTest(checker="regex"):
559
+ self.assertIsNone(strict_fragmented_abspath_pattern.fullmatch(data))
560
+ with self.subTest(checker="parser"):
561
+ with self.assertRaises(pp.ParseException):
562
+ strict_fragmented_abspath_parser.parse_string(data, parse_all=True)
530
563
 
531
564
  data_email_address_pattern = [
532
565
  ("someone@dummy.com",),
@@ -543,8 +576,10 @@ class StrUtilsTest(unittest.TestCase):
543
576
  @ddt.idata(data_email_address_pattern)
544
577
  @ddt.unpack
545
578
  def test_email_address_pattern(self, data):
546
- self.assertIsNotNone(email_address_pattern.match(data))
547
- self.assertIsNotNone(email_address_parser.parse_string(data, parse_all=True))
579
+ with self.subTest(checker="regex"):
580
+ self.assertIsNotNone(email_address_pattern.fullmatch(data))
581
+ with self.subTest(checker="parser"):
582
+ self.assertIsNotNone(email_address_parser.parse_string(data, parse_all=True))
548
583
 
549
584
  data_email_address_pattern__bad_cases = [
550
585
  ("",),
@@ -561,9 +596,48 @@ class StrUtilsTest(unittest.TestCase):
561
596
  @ddt.idata(data_email_address_pattern__bad_cases)
562
597
  @ddt.unpack
563
598
  def test_email_address_pattern__bad_cases(self, data):
564
- self.assertIsNone(email_address_pattern.match(data))
565
- with self.assertRaises(pp.ParseException):
566
- email_address_parser.parse_string(data, parse_all=True)
599
+ with self.subTest(checker="regex"):
600
+ self.assertIsNone(email_address_pattern.fullmatch(data))
601
+ with self.subTest(checker="parser"):
602
+ with self.assertRaises(pp.ParseException):
603
+ email_address_parser.parse_string(data, parse_all=True)
604
+
605
+ data_numeric_version_pattern = [
606
+ ("0",),
607
+ ("1",),
608
+ ("1.1",),
609
+ ("1.1.1",),
610
+ ("1.1.1.1",),
611
+ ("10.20.30.40.50",),
612
+ ]
613
+
614
+ @ddt.idata(data_numeric_version_pattern)
615
+ @ddt.unpack
616
+ def test_numeric_version_pattern(self, data):
617
+ with self.subTest(checker="regex"):
618
+ self.assertIsNotNone(numeric_version_pattern.fullmatch(data))
619
+ with self.subTest(checker="parser"):
620
+ self.assertIsNotNone(numeric_version_parser.parse_string(data, parse_all=True))
621
+
622
+ data_numeric_version_pattern__bad_cases = [
623
+ ("",),
624
+ ("01",),
625
+ ("1.01",),
626
+ ("1.",),
627
+ (".1",),
628
+ ("1..2",),
629
+ ("1.2-alpha",),
630
+ ("1.2+build",),
631
+ ]
632
+
633
+ @ddt.idata(data_numeric_version_pattern__bad_cases)
634
+ @ddt.unpack
635
+ def test_numeric_version_pattern__bad_cases(self, data):
636
+ with self.subTest(checker="regex"):
637
+ self.assertIsNone(numeric_version_pattern.fullmatch(data))
638
+ with self.subTest(checker="parser"):
639
+ with self.assertRaises(pp.ParseException):
640
+ numeric_version_parser.parse_string(data, parse_all=True)
567
641
 
568
642
  data_semver_pattern = [
569
643
  ("0.0.0",),
@@ -574,13 +648,22 @@ class StrUtilsTest(unittest.TestCase):
574
648
  ("1.0.0-alpha+build.1",),
575
649
  ("1.0.0-0.3.7",),
576
650
  ("1.0.0-x.7.z.92",),
651
+ ("1.0.0-alpha+beta-",),
652
+ ("1.2.3-alpha-beta",),
653
+ ("1.2.3-1.alphas-after-digits.2",),
654
+ ("1.2.3-01-leading-zero-but-this-is-a-string",),
655
+ ("1.2.3+some-hash",),
656
+ ("1.2.3+build.01",),
657
+ ("1.2.3-x-y-z.--+anything-after-plus-is-ok",),
577
658
  ]
578
659
 
579
660
  @ddt.idata(data_semver_pattern)
580
661
  @ddt.unpack
581
662
  def test_semver_pattern(self, data):
582
- self.assertIsNotNone(semver_pattern.match(data))
583
- self.assertIsNotNone(semver_parser.parse_string(data, parse_all=True))
663
+ with self.subTest(checker="regex"):
664
+ self.assertIsNotNone(semver_pattern.fullmatch(data))
665
+ with self.subTest(checker="parser"):
666
+ self.assertIsNotNone(semver_parser.parse_string(data, parse_all=True))
584
667
 
585
668
  data_semver_pattern__bad_cases = [
586
669
  ("",),
@@ -593,15 +676,22 @@ class StrUtilsTest(unittest.TestCase):
593
676
  ("1.0.0-",),
594
677
  ("1.0.0+",),
595
678
  ("1.0.0-alpha+",),
596
- ("1.0.0-alpha+beta-",),
679
+ ("1.2.3-01",),
680
+ ("1.2.3-alpha.01",),
681
+ ("1.2.3-001a.01",),
682
+ ("1.2.3-alpha..1",),
683
+ ("1.2.3+build..1",),
684
+ ("1.2.3-alpha+some_hash",),
597
685
  ]
598
686
 
599
687
  @ddt.idata(data_semver_pattern__bad_cases)
600
688
  @ddt.unpack
601
689
  def test_semver_pattern__bad_cases(self, data):
602
- self.assertIsNone(semver_pattern.match(data))
603
- with self.assertRaises(pp.ParseException):
604
- semver_parser.parse_string(data, parse_all=True)
690
+ with self.subTest(checker="regex"):
691
+ self.assertIsNone(semver_pattern.fullmatch(data))
692
+ with self.subTest(checker="parser"):
693
+ with self.assertRaises(pp.ParseException):
694
+ semver_parser.parse_string(data, parse_all=True)
605
695
 
606
696
  data_colon_tag_pattern = [
607
697
  ("dummy",),
@@ -613,8 +703,10 @@ class StrUtilsTest(unittest.TestCase):
613
703
  @ddt.idata(data_colon_tag_pattern)
614
704
  @ddt.unpack
615
705
  def test_colon_tag_pattern(self, data):
616
- self.assertIsNotNone(colon_tag_pattern.match(data))
617
- self.assertIsNotNone(colon_tag_parser.parse_string(data, parse_all=True))
706
+ with self.subTest(checker="regex"):
707
+ self.assertIsNotNone(colon_tag_pattern.fullmatch(data))
708
+ with self.subTest(checker="parser"):
709
+ self.assertIsNotNone(colon_tag_parser.parse_string(data, parse_all=True))
618
710
 
619
711
  data_colon_tag_pattern__bad_cases = [
620
712
  ("",),
@@ -630,9 +722,11 @@ class StrUtilsTest(unittest.TestCase):
630
722
  @ddt.idata(data_colon_tag_pattern__bad_cases)
631
723
  @ddt.unpack
632
724
  def test_colon_tag_pattern__bad_cases(self, data):
633
- self.assertIsNone(colon_tag_pattern.match(data))
634
- with self.assertRaises(pp.ParseException):
635
- colon_tag_parser.parse_string(data, parse_all=True)
725
+ with self.subTest(checker="regex"):
726
+ self.assertIsNone(colon_tag_pattern.fullmatch(data))
727
+ with self.subTest(checker="parser"):
728
+ with self.assertRaises(pp.ParseException):
729
+ colon_tag_parser.parse_string(data, parse_all=True)
636
730
 
637
731
  data_slash_tag_pattern = [
638
732
  ("dummy",),
@@ -644,8 +738,10 @@ class StrUtilsTest(unittest.TestCase):
644
738
  @ddt.idata(data_slash_tag_pattern)
645
739
  @ddt.unpack
646
740
  def test_slash_tag_pattern(self, data):
647
- self.assertIsNotNone(slash_tag_pattern.match(data))
648
- self.assertIsNotNone(slash_tag_parser.parse_string(data, parse_all=True))
741
+ with self.subTest(checker="regex"):
742
+ self.assertIsNotNone(slash_tag_pattern.fullmatch(data))
743
+ with self.subTest(checker="parser"):
744
+ self.assertIsNotNone(slash_tag_parser.parse_string(data, parse_all=True))
649
745
 
650
746
  data_slash_tag_pattern__bad_cases = [
651
747
  ("",),
@@ -661,9 +757,11 @@ class StrUtilsTest(unittest.TestCase):
661
757
  @ddt.idata(data_slash_tag_pattern__bad_cases)
662
758
  @ddt.unpack
663
759
  def test_slash_tag_pattern__bad_cases(self, data):
664
- self.assertIsNone(slash_tag_pattern.match(data))
665
- with self.assertRaises(pp.ParseException):
666
- slash_tag_parser.parse_string(data, parse_all=True)
760
+ with self.subTest(checker="regex"):
761
+ self.assertIsNone(slash_tag_pattern.fullmatch(data))
762
+ with self.subTest(checker="parser"):
763
+ with self.assertRaises(pp.ParseException):
764
+ slash_tag_parser.parse_string(data, parse_all=True)
667
765
 
668
766
  data_topic_pattern = [
669
767
  ("/dummy_sensor",),
@@ -676,8 +774,10 @@ class StrUtilsTest(unittest.TestCase):
676
774
  @ddt.idata(data_topic_pattern)
677
775
  @ddt.unpack
678
776
  def test_topic_pattern(self, data):
679
- self.assertIsNotNone(topic_pattern.match(data))
680
- self.assertIsNotNone(topic_parser.parse_string(data, parse_all=True))
777
+ with self.subTest(checker="regex"):
778
+ self.assertIsNotNone(topic_pattern.fullmatch(data))
779
+ with self.subTest(checker="parser"):
780
+ self.assertIsNotNone(topic_parser.parse_string(data, parse_all=True))
681
781
 
682
782
  data_topic_pattern__bad_cases = [
683
783
  ("",),
@@ -693,9 +793,11 @@ class StrUtilsTest(unittest.TestCase):
693
793
  @ddt.idata(data_topic_pattern__bad_cases)
694
794
  @ddt.unpack
695
795
  def test_topic_pattern__bad_cases(self, data):
696
- self.assertIsNone(topic_pattern.match(data))
697
- with self.assertRaises(pp.ParseException):
698
- topic_parser.parse_string(data, parse_all=True)
796
+ with self.subTest(checker="regex"):
797
+ self.assertIsNone(topic_pattern.fullmatch(data))
798
+ with self.subTest(checker="parser"):
799
+ with self.assertRaises(pp.ParseException):
800
+ topic_parser.parse_string(data, parse_all=True)
699
801
 
700
802
  data_vin_code_pattern = [
701
803
  ("00000000000000000",),
@@ -707,8 +809,10 @@ class StrUtilsTest(unittest.TestCase):
707
809
  @ddt.idata(data_vin_code_pattern)
708
810
  @ddt.unpack
709
811
  def test_vin_code_pattern(self, data):
710
- self.assertIsNotNone(vin_code_pattern.match(data))
711
- self.assertIsNotNone(vin_code_parser.parse_string(data, parse_all=True))
812
+ with self.subTest(checker="regex"):
813
+ self.assertIsNotNone(vin_code_pattern.fullmatch(data))
814
+ with self.subTest(checker="parser"):
815
+ self.assertIsNotNone(vin_code_parser.parse_string(data, parse_all=True))
712
816
 
713
817
  data_vin_code_pattern__bad_cases = [
714
818
  ("",),
@@ -723,9 +827,11 @@ class StrUtilsTest(unittest.TestCase):
723
827
  @ddt.idata(data_vin_code_pattern__bad_cases)
724
828
  @ddt.unpack
725
829
  def test_vin_code_pattern__bad_cases(self, data):
726
- self.assertIsNone(vin_code_pattern.match(data))
727
- with self.assertRaises(pp.ParseException):
728
- vin_code_parser.parse_string(data, parse_all=True)
830
+ with self.subTest(checker="regex"):
831
+ self.assertIsNone(vin_code_pattern.fullmatch(data))
832
+ with self.subTest(checker="parser"):
833
+ with self.assertRaises(pp.ParseException):
834
+ vin_code_parser.parse_string(data, parse_all=True)
729
835
 
730
836
  data_parse_user_name = [
731
837
  ("dummy.person", UserName("dummy", "person", 0)),