plexus-python-common 1.0.22__tar.gz → 1.0.24__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 (80) hide show
  1. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/PKG-INFO +6 -3
  2. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/pyproject.toml +11 -3
  3. plexus_python_common-1.0.24/src/plexus/common/utils/apiutils.py +31 -0
  4. plexus_python_common-1.0.24/src/plexus/common/utils/sqlutils.py +9 -0
  5. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/strutils.py +47 -21
  6. plexus_python_common-1.0.24/src/plexus/common/utils/testutils.py +42 -0
  7. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/PKG-INFO +6 -3
  8. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/SOURCES.txt +5 -1
  9. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/requires.txt +6 -2
  10. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/ormutils_test.py +3 -2
  11. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/strutils_test.py +38 -1
  12. plexus_python_common-1.0.24/test/plexus_tests/common/utils/testutils_test.py +50 -0
  13. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/.editorconfig +0 -0
  14. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/.github/workflows/pr.yml +0 -0
  15. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/.github/workflows/push.yml +0 -0
  16. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/.gitignore +0 -0
  17. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/MANIFEST.in +0 -0
  18. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/README.md +0 -0
  19. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/VERSION +0 -0
  20. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/jsonutils/dummy.0.jsonl +0 -0
  21. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/jsonutils/dummy.1.jsonl +0 -0
  22. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/jsonutils/dummy.2.jsonl +0 -0
  23. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  24. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  25. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  26. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  27. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  28. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  29. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  30. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  31. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  32. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  33. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/0-dummy +0 -0
  34. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/1-dummy +0 -0
  35. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/2-dummy +0 -0
  36. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.0.0.jsonl +0 -0
  37. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.0.0.vol-0.jsonl +0 -0
  38. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.0.jsonl +0 -0
  39. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.1.1.jsonl +0 -0
  40. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.1.1.vol-1.jsonl +0 -0
  41. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.1.jsonl +0 -0
  42. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.2.2.jsonl +0 -0
  43. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.2.2.vol-2.jsonl +0 -0
  44. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.2.jsonl +0 -0
  45. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.csv.part0 +0 -0
  46. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.csv.part1 +0 -0
  47. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.csv.part2 +0 -0
  48. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/resources/unittest/shutils/dummy.txt +0 -0
  49. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/setup.cfg +0 -0
  50. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/setup.py +0 -0
  51. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/__init__.py +0 -0
  52. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/carto/OSMFile.py +0 -0
  53. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/carto/OSMNode.py +0 -0
  54. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/carto/OSMTags.py +0 -0
  55. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/carto/OSMWay.py +0 -0
  56. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/carto/__init__.py +0 -0
  57. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/config.py +0 -0
  58. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/pose.py +0 -0
  59. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/proj.py +0 -0
  60. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/__init__.py +0 -0
  61. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/bagutils.py +0 -0
  62. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/datautils.py +0 -0
  63. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/jsonutils.py +0 -0
  64. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/ormutils.py +0 -0
  65. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/s3utils.py +0 -0
  66. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus/common/utils/shutils.py +0 -0
  67. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/dependency_links.txt +0 -0
  68. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/not-zip-safe +0 -0
  69. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/src/plexus_python_common.egg-info/top_level.txt +0 -0
  70. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_test.py +0 -0
  71. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/__init__.py +0 -0
  72. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/carto/osm_file_test.py +0 -0
  73. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/carto/osm_tags_test.py +0 -0
  74. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/pose_test.py +0 -0
  75. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/proj_test.py +0 -0
  76. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/bagutils_test.py +0 -0
  77. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/datautils_test.py +0 -0
  78. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/jsonutils_test.py +0 -0
  79. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/s3utils_test.py +0 -0
  80. {plexus_python_common-1.0.22 → plexus_python_common-1.0.24}/test/plexus_tests/common/utils/shutils_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.22
3
+ Version: 1.0.24
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -16,12 +16,15 @@ Requires-Dist: pyparsing>=3.2
16
16
  Requires-Dist: pyproj>=3.6
17
17
  Requires-Dist: pyquaternion>=0.9
18
18
  Requires-Dist: sqlalchemy>=2.0
19
- Requires-Dist: sqlmodel
20
19
  Requires-Dist: rich>=13.9
20
+ Requires-Dist: textcase>=0.4
21
21
  Requires-Dist: ujson>=5.9
22
22
  Requires-Dist: iker-python-common[all]>=1.0
23
23
  Provides-Extra: all
24
- Requires-Dist: plexus-python-common; extra == "all"
24
+ Requires-Dist: plexus-python-common[api]; extra == "all"
25
+ Provides-Extra: api
26
+ Requires-Dist: fastapi>=0.119.0; extra == "api"
27
+ Requires-Dist: sqlmodel>=0.0.25; extra == "api"
25
28
  Provides-Extra: test
26
29
  Requires-Dist: ddt>=1.7; extra == "test"
27
30
  Requires-Dist: moto[all,ec2,s3]>=5.1; extra == "test"
@@ -18,11 +18,15 @@ dev = [
18
18
  "pyproj>=3.6",
19
19
  "pyquaternion>=0.9",
20
20
  "sqlalchemy>=2.0",
21
- "sqlmodel",
22
21
  "rich>=13.9",
22
+ "textcase>=0.4",
23
23
  "ujson>=5.9",
24
24
  "iker-python-common[all]>=1.0",
25
25
  ]
26
+ api = [
27
+ "fastapi>=0.119.0",
28
+ "sqlmodel>=0.0.25",
29
+ ]
26
30
  test = [
27
31
  "ddt>=1.7",
28
32
  "moto[ec2,s3,all]>=5.1",
@@ -53,15 +57,19 @@ dependencies = [
53
57
  "pyproj>=3.6",
54
58
  "pyquaternion>=0.9",
55
59
  "sqlalchemy>=2.0",
56
- "sqlmodel",
57
60
  "rich>=13.9",
61
+ "textcase>=0.4",
58
62
  "ujson>=5.9",
59
63
  "iker-python-common[all]>=1.0",
60
64
  ]
61
65
 
62
66
  [project.optional-dependencies]
63
67
  all = [
64
- "plexus-python-common",
68
+ "plexus-python-common[api]",
69
+ ]
70
+ api = [
71
+ "fastapi>=0.119.0",
72
+ "sqlmodel>=0.0.25",
65
73
  ]
66
74
  test = [
67
75
  "ddt>=1.7",
@@ -0,0 +1,31 @@
1
+ from contextlib import AbstractContextManager
2
+
3
+ import sqlalchemy.orm as sa_orm
4
+ from fastapi import HTTPException
5
+
6
+ __all__ = [
7
+ "managed_db_session",
8
+ ]
9
+
10
+
11
+ class DBSessionExceptionManager(AbstractContextManager):
12
+ def __init__(self, db: sa_orm.Session, commit_on_exit: bool):
13
+ self.db = db
14
+ self.commit_on_exit = commit_on_exit
15
+
16
+ def __enter__(self):
17
+ return self
18
+
19
+ def __exit__(self, exc_type, exc_value, traceback):
20
+ if isinstance(exc_type, HTTPException):
21
+ return False # Propagate HTTPException
22
+ if exc_type is not None:
23
+ self.db.rollback() # Raise a new HTTPException (or any other exception type)
24
+ raise HTTPException(status_code=500, detail=str(exc_value)) from exc_value
25
+ if self.commit_on_exit:
26
+ self.db.commit()
27
+ return True
28
+
29
+
30
+ def managed_db_session(db: sa_orm.Session, commit_on_exit: bool = True) -> DBSessionExceptionManager:
31
+ return DBSessionExceptionManager(db, commit_on_exit)
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "escape_sql_like",
3
+ ]
4
+
5
+
6
+ def escape_sql_like(s: str | None) -> str:
7
+ if s is None:
8
+ raise ValueError("input string cannot be None")
9
+ return s.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
@@ -22,6 +22,8 @@ __all__ = [
22
22
  "strict_relpath_parser",
23
23
  "strict_abspath_pattern",
24
24
  "strict_abspath_parser",
25
+ "email_address_pattern",
26
+ "email_address_parser",
25
27
  "semver_pattern",
26
28
  "semver_parser",
27
29
  "colon_tag_pattern",
@@ -74,16 +76,39 @@ def make_string_parser(element: pp.ParserElement) -> pp.ParserElement:
74
76
  return pp.Combine(pp.StringStart() + element + pp.StringEnd())
75
77
 
76
78
 
77
- lowers_regexp: re.Pattern[str] = re.compile(r"[a-z]+")
78
- uppers_regexp: re.Pattern[str] = re.compile(r"[A-Z]+")
79
- digits_regexp: re.Pattern[str] = re.compile(r"[0-9]+")
80
- lower_digits_regexp: re.Pattern[str] = re.compile(r"[a-z0-9]+")
81
- upper_digits_regexp: re.Pattern[str] = re.compile(r"[A-Z0-9]+")
82
- alpha_digits_regexp: re.Pattern[str] = re.compile(r"[a-zA-Z0-9]+")
83
- hex_digits_regexp: re.Pattern[str] = re.compile(r"[a-f0-9]+")
84
- lower_identifier_regexp: re.Pattern[str] = re.compile(r"[a-z][a-z0-9]*")
85
- upper_identifier_regexp: re.Pattern[str] = re.compile(r"[A-Z][A-Z0-9]*")
86
- strict_chars_regexp: re.Pattern[str] = re.compile(r"[a-zA-Z0-9._-]+")
79
+ underscore_token: pp.ParserElement = pp.Char("_")
80
+ hyphen_token: pp.ParserElement = pp.Char("-")
81
+ period_token: pp.ParserElement = pp.Char(".")
82
+ colon_token: pp.ParserElement = pp.Char(":")
83
+ slash_token: pp.ParserElement = pp.Char("/")
84
+ plus_token: pp.ParserElement = pp.Char("+")
85
+
86
+ lower_regexp: re.Pattern[str] = re.compile(r"[a-z]")
87
+ upper_regexp: re.Pattern[str] = re.compile(r"[A-Z]")
88
+ digit_regexp: re.Pattern[str] = re.compile(r"[0-9]")
89
+ lower_digit_regexp: re.Pattern[str] = re.compile(r"[a-z0-9]")
90
+ upper_digit_regexp: re.Pattern[str] = re.compile(r"[A-Z0-9]")
91
+ alpha_digit_regexp: re.Pattern[str] = re.compile(r"[a-zA-Z0-9]")
92
+ hex_digit_regexp: re.Pattern[str] = re.compile(r"[a-f0-9]")
93
+
94
+ lowers_regexp: re.Pattern[str] = re.compile(rf"{lower_regexp.pattern}+")
95
+ uppers_regexp: re.Pattern[str] = re.compile(rf"{upper_regexp.pattern}+")
96
+ digits_regexp: re.Pattern[str] = re.compile(rf"{digit_regexp.pattern}+")
97
+ lower_digits_regexp: re.Pattern[str] = re.compile(rf"{lower_digit_regexp.pattern}+")
98
+ upper_digits_regexp: re.Pattern[str] = re.compile(rf"{upper_digit_regexp.pattern}+")
99
+ alpha_digits_regexp: re.Pattern[str] = re.compile(rf"{alpha_digit_regexp.pattern}+")
100
+ hex_digits_regexp: re.Pattern[str] = re.compile(rf"{hex_digit_regexp.pattern}+")
101
+ lower_identifier_regexp: re.Pattern[str] = re.compile(rf"{lower_regexp.pattern}{lower_digit_regexp.pattern}*")
102
+ upper_identifier_regexp: re.Pattern[str] = re.compile(rf"{upper_regexp.pattern}{upper_digit_regexp.pattern}*")
103
+ strict_chars_regexp: re.Pattern[str] = re.compile(rf"({alpha_digit_regexp.pattern}|[._-])+")
104
+
105
+ lower_element: pp.ParserElement = pp.Regex(lower_regexp.pattern)
106
+ upper_element: pp.ParserElement = pp.Regex(upper_regexp.pattern)
107
+ digit_element: pp.ParserElement = pp.Regex(digit_regexp.pattern)
108
+ lower_digit_element: pp.ParserElement = pp.Regex(lower_digit_regexp.pattern)
109
+ upper_digit_element: pp.ParserElement = pp.Regex(upper_digit_regexp.pattern)
110
+ alpha_digit_element: pp.ParserElement = pp.Regex(alpha_digit_regexp.pattern)
111
+ hex_digit_element: pp.ParserElement = pp.Regex(hex_digit_regexp.pattern)
87
112
 
88
113
  lowers_element: pp.ParserElement = pp.Regex(lowers_regexp.pattern)
89
114
  uppers_element: pp.ParserElement = pp.Regex(uppers_regexp.pattern)
@@ -96,24 +121,17 @@ lower_identifier_element: pp.ParserElement = pp.Regex(lower_identifier_regexp.pa
96
121
  upper_identifier_element: pp.ParserElement = pp.Regex(upper_identifier_regexp.pattern)
97
122
  strict_chars_element: pp.ParserElement = pp.Regex(strict_chars_regexp.pattern)
98
123
 
99
- underscore_token: pp.ParserElement = pp.Char("_")
100
- hyphen_token: pp.ParserElement = pp.Char("-")
101
- period_token: pp.ParserElement = pp.Char(".")
102
- colon_token: pp.ParserElement = pp.Char(":")
103
- slash_token: pp.ParserElement = pp.Char("/")
104
- plus_token: pp.ParserElement = pp.Char("+")
105
-
106
124
  basic_datetime_regexp: re.Pattern[str] = re.compile(r"\d{8}T\d{6}")
107
125
  extended_datetime_regexp: re.Pattern[str] = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}")
108
126
 
109
127
  basic_datetime_element: pp.ParserElement = pp.Regex(basic_datetime_regexp.pattern)
110
128
  extended_datetime_element: pp.ParserElement = pp.Regex(extended_datetime_regexp.pattern)
111
129
 
112
- number_regexp: re.Pattern[str] = re.compile(r"0|([1-9][0-9]*)")
113
- positive_number_regexp: re.Pattern[str] = re.compile(r"[1-9][0-9]*")
130
+ positive_number_regexp: re.Pattern[str] = re.compile(rf"[1-9]{digit_regexp.pattern}*")
131
+ number_regexp: re.Pattern[str] = re.compile(rf"0|({positive_number_regexp.pattern})")
114
132
 
115
- number_element: pp.ParserElement = pp.Regex(number_regexp.pattern)
116
133
  positive_number_element: pp.ParserElement = pp.Regex(positive_number_regexp.pattern)
134
+ number_element: pp.ParserElement = pp.Regex(number_regexp.pattern)
117
135
 
118
136
  snake_case_regexp: re.Pattern[str] = re.compile(
119
137
  rf"{lower_digits_regexp.pattern}(?:_{lower_digits_regexp.pattern})*")
@@ -129,7 +147,8 @@ kebab_case_element: pp.ParserElement = pp.Combine(
129
147
  dot_case_element: pp.ParserElement = pp.Combine(
130
148
  lower_digits_element + (period_token + lower_digits_element)[...])
131
149
 
132
- uuid_regexp: re.Pattern[str] = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
150
+ uuid_regexp: re.Pattern[str] = re.compile(
151
+ rf"{hex_digit_regexp.pattern}{{8}}(-{hex_digit_regexp.pattern}{{4}}){{3}}-{hex_digit_regexp.pattern}{{12}}")
133
152
  uuid_element: pp.ParserElement = pp.Regex(uuid_regexp.pattern)
134
153
 
135
154
  strict_relpath_regexp: re.Pattern[str] = re.compile(
@@ -146,6 +165,10 @@ strict_relpath_element: pp.ParserElement = pp.Combine(
146
165
  strict_abspath_element: pp.ParserElement = pp.Combine(
147
166
  slash_token + (strict_path_chars_element + slash_token)[...] + strict_path_chars_element[0, 1])
148
167
 
168
+ email_address_regexp: re.Pattern[str] = re.compile(
169
+ rf"(({lower_digit_regexp.pattern}|[_-])+)(?:\.({lower_digit_regexp.pattern}|[_-])+)*@(?:{kebab_case_regexp.pattern}\.)+({lower_digit_regexp.pattern}{{2,63}})")
170
+ email_address_element: pp.ParserElement = pp.Regex(email_address_regexp.pattern)
171
+
149
172
  semver_regexp: re.Pattern[str] = re.compile(
150
173
  rf"({number_regexp.pattern})\.({number_regexp.pattern})\.({number_regexp.pattern})"
151
174
  rf"(?:-{alpha_digits_regexp.pattern}(?:\.{alpha_digits_regexp.pattern})*)?"
@@ -184,6 +207,9 @@ strict_relpath_parser = make_string_parser(strict_relpath_element)
184
207
  strict_abspath_pattern = make_string_pattern(strict_abspath_regexp)
185
208
  strict_abspath_parser = make_string_parser(strict_abspath_element)
186
209
 
210
+ email_address_pattern = make_string_pattern(email_address_regexp)
211
+ email_address_parser = make_string_parser(email_address_element)
212
+
187
213
  semver_pattern = make_string_pattern(semver_regexp)
188
214
  semver_parser = make_string_parser(semver_element)
189
215
 
@@ -0,0 +1,42 @@
1
+ import textcase
2
+ from iker.common.utils.jsonutils import JsonType
3
+ from iker.common.utils.jsonutils import json_difference, json_reformat
4
+
5
+ from plexus.common.utils.datautils import compute_vin_code_check_digit
6
+
7
+ __all__ = [
8
+ "generate_dummy_uuid_str",
9
+ "generate_dummy_vin_code",
10
+ "case_insensitive_json_compare",
11
+ ]
12
+
13
+
14
+ def generate_dummy_uuid_str(*nums: int) -> str:
15
+ if len(nums) > 8:
16
+ raise ValueError("a maximum of 8 integers can be provided")
17
+ if not all(0 <= num <= 0xFFFF for num in nums):
18
+ raise ValueError("all integers must be in the range 0 to 65535 (0xFFFF)")
19
+ i0, i1, i2, i3, i4, i5, i6, i7 = list(nums) + [0] * (8 - len(nums))
20
+ return f"{i0:04x}{i1:04x}-{i2:04x}-{i3:04x}-{i4:04x}-{i5:04x}{i6:04x}{i7:04x}"
21
+
22
+
23
+ def generate_dummy_vin_code(*nums: int) -> str:
24
+ if len(nums) > 4:
25
+ raise ValueError("a maximum of 4 integers can be provided")
26
+ if not all(0 <= num <= 9999 for num in nums):
27
+ raise ValueError("all integers must be in the range 0 to 9999")
28
+ i0, i1, i2, i3 = list(nums) + [0] * (4 - len(nums))
29
+ unchecked_vin_code = f"{i0:04d}{i1:04d}0{i2:04d}{i3:04d}"
30
+ check_digit = compute_vin_code_check_digit(unchecked_vin_code)
31
+ return unchecked_vin_code[:8] + check_digit + unchecked_vin_code[-8:]
32
+
33
+
34
+ def case_insensitive_json_compare(a: JsonType, b: JsonType, *, print_diff_messages: bool = True) -> bool:
35
+ identical = True
36
+ for node_path, diff_message in json_difference(json_reformat(a, key_formatter=textcase.camel),
37
+ json_reformat(b, key_formatter=textcase.camel),
38
+ []):
39
+ if print_diff_messages:
40
+ print(node_path, diff_message)
41
+ identical = False
42
+ return identical
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexus-python-common
3
- Version: 1.0.22
3
+ Version: 1.0.24
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -16,12 +16,15 @@ Requires-Dist: pyparsing>=3.2
16
16
  Requires-Dist: pyproj>=3.6
17
17
  Requires-Dist: pyquaternion>=0.9
18
18
  Requires-Dist: sqlalchemy>=2.0
19
- Requires-Dist: sqlmodel
20
19
  Requires-Dist: rich>=13.9
20
+ Requires-Dist: textcase>=0.4
21
21
  Requires-Dist: ujson>=5.9
22
22
  Requires-Dist: iker-python-common[all]>=1.0
23
23
  Provides-Extra: all
24
- Requires-Dist: plexus-python-common; extra == "all"
24
+ Requires-Dist: plexus-python-common[api]; extra == "all"
25
+ Provides-Extra: api
26
+ Requires-Dist: fastapi>=0.119.0; extra == "api"
27
+ Requires-Dist: sqlmodel>=0.0.25; extra == "api"
25
28
  Provides-Extra: test
26
29
  Requires-Dist: ddt>=1.7; extra == "test"
27
30
  Requires-Dist: moto[all,ec2,s3]>=5.1; extra == "test"
@@ -46,13 +46,16 @@ src/plexus/common/carto/OSMTags.py
46
46
  src/plexus/common/carto/OSMWay.py
47
47
  src/plexus/common/carto/__init__.py
48
48
  src/plexus/common/utils/__init__.py
49
+ src/plexus/common/utils/apiutils.py
49
50
  src/plexus/common/utils/bagutils.py
50
51
  src/plexus/common/utils/datautils.py
51
52
  src/plexus/common/utils/jsonutils.py
52
53
  src/plexus/common/utils/ormutils.py
53
54
  src/plexus/common/utils/s3utils.py
54
55
  src/plexus/common/utils/shutils.py
56
+ src/plexus/common/utils/sqlutils.py
55
57
  src/plexus/common/utils/strutils.py
58
+ src/plexus/common/utils/testutils.py
56
59
  src/plexus_python_common.egg-info/PKG-INFO
57
60
  src/plexus_python_common.egg-info/SOURCES.txt
58
61
  src/plexus_python_common.egg-info/dependency_links.txt
@@ -71,4 +74,5 @@ test/plexus_tests/common/utils/jsonutils_test.py
71
74
  test/plexus_tests/common/utils/ormutils_test.py
72
75
  test/plexus_tests/common/utils/s3utils_test.py
73
76
  test/plexus_tests/common/utils/shutils_test.py
74
- test/plexus_tests/common/utils/strutils_test.py
77
+ test/plexus_tests/common/utils/strutils_test.py
78
+ test/plexus_tests/common/utils/testutils_test.py
@@ -8,13 +8,17 @@ pyparsing>=3.2
8
8
  pyproj>=3.6
9
9
  pyquaternion>=0.9
10
10
  sqlalchemy>=2.0
11
- sqlmodel
12
11
  rich>=13.9
12
+ textcase>=0.4
13
13
  ujson>=5.9
14
14
  iker-python-common[all]>=1.0
15
15
 
16
16
  [all]
17
- plexus-python-common
17
+ plexus-python-common[api]
18
+
19
+ [api]
20
+ fastapi>=0.119.0
21
+ sqlmodel>=0.0.25
18
22
 
19
23
  [test]
20
24
  ddt>=1.7
@@ -12,6 +12,7 @@ from iker.common.utils.jsonutils import JsonType
12
12
  from iker.common.utils.randutils import randomizer
13
13
  from sqlmodel import Field, SQLModel
14
14
 
15
+ from plexus.common.utils.apiutils import managed_db_session
15
16
  from plexus.common.utils.ormutils import (
16
17
  db_activate_revision_model,
17
18
  db_activate_snapshot_model,
@@ -954,7 +955,7 @@ def test_make_snapshot_model_trigger(fixture_postgresql_test_proc, fixture_postg
954
955
  dummy_json=rng.random_json_object(5),
955
956
  )
956
957
 
957
- with maker.make_session() as session:
958
+ with maker.make_session() as session, managed_db_session(session):
958
959
  session.execute(sa.sql.text("SET TIMEZONE TO 'UTC'"))
959
960
  session.commit()
960
961
 
@@ -1040,7 +1041,7 @@ def test_make_revision_model_trigger(fixture_postgresql_test_proc, fixture_postg
1040
1041
  dummy_json=rng.random_json_object(5),
1041
1042
  )
1042
1043
 
1043
- with maker.make_session() as session:
1044
+ with maker.make_session() as session, managed_db_session(session):
1044
1045
  session.execute(sa.sql.text("SET TIMEZONE TO 'UTC'"))
1045
1046
  session.commit()
1046
1047
 
@@ -7,10 +7,10 @@ from iker.common.utils.dtutils import dt_parse_iso
7
7
  from plexus.common.utils.strutils import BagName, UserEmail, UserName, VehicleName
8
8
  from plexus.common.utils.strutils import colon_tag_parser, colon_tag_pattern, slash_tag_parser, slash_tag_pattern
9
9
  from plexus.common.utils.strutils import dot_case_parser, dot_case_pattern
10
+ from plexus.common.utils.strutils import email_address_parser, email_address_pattern, semver_parser, semver_pattern
10
11
  from plexus.common.utils.strutils import hex_string_parser, hex_string_pattern
11
12
  from plexus.common.utils.strutils import kebab_case_parser, kebab_case_pattern
12
13
  from plexus.common.utils.strutils import parse_bag_name, parse_user_email, parse_user_name, parse_vehicle_name
13
- from plexus.common.utils.strutils import semver_parser, semver_pattern
14
14
  from plexus.common.utils.strutils import snake_case_parser, snake_case_pattern
15
15
  from plexus.common.utils.strutils import strict_abspath_parser, strict_abspath_pattern
16
16
  from plexus.common.utils.strutils import strict_relpath_parser, strict_relpath_pattern
@@ -377,6 +377,43 @@ class StrUtilsTest(unittest.TestCase):
377
377
  with self.assertRaises(pp.ParseException):
378
378
  strict_abspath_parser.parse_string(data, parse_all=True)
379
379
 
380
+ data_email_address_pattern = [
381
+ ("someone@dummy.com",),
382
+ ("some.one@dummy.com",),
383
+ ("some.1@dummy.com",),
384
+ ("some-one@dummy.com",),
385
+ ("someone-@dummy.com",),
386
+ ("someone@dummy.company.com",),
387
+ ("some.one@dummy-company.com",),
388
+ ("some.1@dummy.company-company.com",),
389
+ ("some_one@dummy-1st-company.co-ltd.com",),
390
+ ]
391
+
392
+ @ddt.idata(data_email_address_pattern)
393
+ @ddt.unpack
394
+ def test_email_address_pattern(self, data):
395
+ self.assertIsNotNone(email_address_pattern.match(data))
396
+ self.assertIsNotNone(email_address_parser.parse_string(data, parse_all=True))
397
+
398
+ data_email_address_pattern__bad_cases = [
399
+ ("",),
400
+ ("some.one",),
401
+ ("@dummy.com",),
402
+ ("someone@dummy",),
403
+ ("someone@dummy.methionylthreonylthreonylglutaminylarginyltyrosylglutamylserylleucine",),
404
+ ("someone+one@dummy.com",),
405
+ ("someone.@dummy.com",),
406
+ ("someone@.dummy.com",),
407
+ ("someone@dummy-.com",),
408
+ ]
409
+
410
+ @ddt.idata(data_email_address_pattern__bad_cases)
411
+ @ddt.unpack
412
+ def test_email_address_pattern__bad_cases(self, data):
413
+ self.assertIsNone(email_address_pattern.match(data))
414
+ with self.assertRaises(pp.ParseException):
415
+ email_address_parser.parse_string(data, parse_all=True)
416
+
380
417
  data_semver_pattern = [
381
418
  ("0.0.0",),
382
419
  ("1.2.3",),
@@ -0,0 +1,50 @@
1
+ import unittest
2
+
3
+ import ddt
4
+
5
+ from plexus.common.utils.testutils import generate_dummy_uuid_str, generate_dummy_vin_code
6
+
7
+
8
+ @ddt.ddt
9
+ class TestUtilsTest(unittest.TestCase):
10
+ data_generate_dummy_uuid_str = [
11
+ ((), "00000000-0000-0000-0000-000000000000"),
12
+ ((0,), "00000000-0000-0000-0000-000000000000"),
13
+ ((0, 0,), "00000000-0000-0000-0000-000000000000"),
14
+ ((0, 0, 0, 0, 0, 0, 0, 0,), "00000000-0000-0000-0000-000000000000"),
15
+ ((1,), "00010000-0000-0000-0000-000000000000"),
16
+ ((1, 2, 3, 4, 5, 6, 7, 8,), "00010002-0003-0004-0005-000600070008"),
17
+ ((0x1, 0x10, 0x100, 0x1000, 0xF, 0xFF, 0xFFF, 0xFFFF,), "00010010-0100-1000-000f-00ff0fffffff"),
18
+ ]
19
+
20
+ @ddt.idata(data_generate_dummy_uuid_str)
21
+ @ddt.unpack
22
+ def test_generate_dummy_uuid_str(self, nums, expect):
23
+ self.assertEqual(generate_dummy_uuid_str(*nums), expect)
24
+
25
+ data_generate_dummy_uuid_str__bad_case = [
26
+ ((0, 0, 0, 0, 0, 0, 0, 0, 0,),),
27
+ ((-1,),),
28
+ ((0x10000,),),
29
+ ]
30
+
31
+ @ddt.idata(data_generate_dummy_uuid_str__bad_case)
32
+ @ddt.unpack
33
+ def test_generate_dummy_uuid_str__bad_case(self, nums):
34
+ with self.assertRaises(ValueError):
35
+ generate_dummy_uuid_str(*nums)
36
+
37
+ data_generate_dummy_vin_code = [
38
+ ((), "00000000000000000"),
39
+ ((0,), "00000000000000000"),
40
+ ((0, 0,), "00000000000000000"),
41
+ ((0, 0, 0, 0,), "00000000000000000"),
42
+ ((1,), "00010000500000000"),
43
+ ((1, 2, 3, 4,), "00010002700030004"),
44
+ ((1, 23, 456, 7890,), "00010023504567890"),
45
+ ]
46
+
47
+ @ddt.idata(data_generate_dummy_vin_code)
48
+ @ddt.unpack
49
+ def test_generate_dummy_vin_code(self, nums, expect):
50
+ self.assertEqual(generate_dummy_vin_code(*nums), expect)