various-api-tools 0.3.4__tar.gz → 1.0.0__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.
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/PKG-INFO +5 -6
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/README.md +4 -4
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/pyproject.toml +12 -8
- various_api_tools-1.0.0/src/various_api_tools/__init__.py +32 -0
- various_api_tools-1.0.0/src/various_api_tools/translators/psycopg/__init__.py +1 -0
- various_api_tools-1.0.0/src/various_api_tools/translators/psycopg/base.py +229 -0
- various_api_tools-1.0.0/src/various_api_tools/translators/psycopg/constants.py +71 -0
- various_api_tools-1.0.0/src/various_api_tools/translators/psycopg/psycopg.py +43 -0
- various_api_tools-1.0.0/src/various_api_tools/translators/psycopg/psycopg2.py +36 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/src/various_api_tools/translators/pydantic.py +4 -1
- various_api_tools-1.0.0/src/various_api_tools/validators/pydantic/__init__.py +1 -0
- various_api_tools-0.3.4/src/various_api_tools/error_maps/pydantic.py → various_api_tools-1.0.0/src/various_api_tools/validators/pydantic/constants.py +50 -3
- various_api_tools-1.0.0/src/various_api_tools/validators/pydantic/utils.py +254 -0
- various_api_tools-1.0.0/tests/translators/test_psycopg.py +97 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/translators/test_psycopg2.py +14 -20
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/validators/test_pydantic.py +31 -15
- various_api_tools-0.3.4/src/various_api_tools/__init__.py +0 -18
- various_api_tools-0.3.4/src/various_api_tools/error_maps/__init__.py +0 -1
- various_api_tools-0.3.4/src/various_api_tools/translators/psycopg2.py +0 -167
- various_api_tools-0.3.4/src/various_api_tools/validators/pydantic.py +0 -221
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/src/various_api_tools/translators/__init__.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/src/various_api_tools/translators/json.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/src/various_api_tools/validators/__init__.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/__init__.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/translators/__init__.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/translators/test_json.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/translators/test_pydantic.py +0 -0
- {various_api_tools-0.3.4 → various_api_tools-1.0.0}/tests/validators/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: various-api-tools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A lightweight utility package for common API-related tasks in Python, including JSON and Pydantic error translators that provide user-friendly Russian messages.
|
|
5
5
|
Author-Email: dkurchigin <kurchigin.dmitry@yandex.ru>
|
|
6
6
|
License: MIT
|
|
@@ -17,7 +17,6 @@ Project-URL: Documentation, https://various-api-tools.dkurchigin.ru/
|
|
|
17
17
|
Project-URL: Source, https://gitverse.ru/dkurchigin/various-api-tools
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
19
|
Requires-Dist: pydantic>=2.12.3
|
|
20
|
-
Requires-Dist: psycopg2-binary>=2.9.10
|
|
21
20
|
Requires-Dist: email-validator>=2.3.0
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
|
|
@@ -29,12 +28,12 @@ Description-Content-Type: text/markdown
|
|
|
29
28
|
|
|
30
29
|
```python
|
|
31
30
|
import json
|
|
32
|
-
from various_api_tools.translators.json import
|
|
31
|
+
from various_api_tools.translators.json import JSONDecodeErrorTranslator
|
|
33
32
|
|
|
34
33
|
try:
|
|
35
34
|
json.loads('{"name": "Alice",}')
|
|
36
35
|
except json.JSONDecodeError as e:
|
|
37
|
-
print(
|
|
36
|
+
print(JSONDecodeErrorTranslator.translate(e))
|
|
38
37
|
|
|
39
38
|
# Output:
|
|
40
39
|
# Ошибка конвертации в формате JSON.
|
|
@@ -44,7 +43,7 @@ except json.JSONDecodeError as e:
|
|
|
44
43
|
|
|
45
44
|
```python
|
|
46
45
|
from pydantic import BaseModel, ValidationError
|
|
47
|
-
from various_api_tools.translators.pydantic import
|
|
46
|
+
from various_api_tools.translators.pydantic import PydanticValidationErrorTranslator
|
|
48
47
|
|
|
49
48
|
class User(BaseModel):
|
|
50
49
|
email: str
|
|
@@ -52,7 +51,7 @@ class User(BaseModel):
|
|
|
52
51
|
try:
|
|
53
52
|
User(email=123)
|
|
54
53
|
except ValidationError as e:
|
|
55
|
-
print(
|
|
54
|
+
print(PydanticValidationErrorTranslator.translate(e.errors()))
|
|
56
55
|
|
|
57
56
|
# Output:
|
|
58
57
|
# Поле: "email". Ошибка: "Невалидное строковое значение(str)";
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
```python
|
|
8
8
|
import json
|
|
9
|
-
from various_api_tools.translators.json import
|
|
9
|
+
from various_api_tools.translators.json import JSONDecodeErrorTranslator
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
12
|
json.loads('{"name": "Alice",}')
|
|
13
13
|
except json.JSONDecodeError as e:
|
|
14
|
-
print(
|
|
14
|
+
print(JSONDecodeErrorTranslator.translate(e))
|
|
15
15
|
|
|
16
16
|
# Output:
|
|
17
17
|
# Ошибка конвертации в формате JSON.
|
|
@@ -21,7 +21,7 @@ except json.JSONDecodeError as e:
|
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
23
|
from pydantic import BaseModel, ValidationError
|
|
24
|
-
from various_api_tools.translators.pydantic import
|
|
24
|
+
from various_api_tools.translators.pydantic import PydanticValidationErrorTranslator
|
|
25
25
|
|
|
26
26
|
class User(BaseModel):
|
|
27
27
|
email: str
|
|
@@ -29,7 +29,7 @@ class User(BaseModel):
|
|
|
29
29
|
try:
|
|
30
30
|
User(email=123)
|
|
31
31
|
except ValidationError as e:
|
|
32
|
-
print(
|
|
32
|
+
print(PydanticValidationErrorTranslator.translate(e.errors()))
|
|
33
33
|
|
|
34
34
|
# Output:
|
|
35
35
|
# Поле: "email". Ошибка: "Невалидное строковое значение(str)";
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "various-api-tools"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "1.0.0"
|
|
4
4
|
description = "A lightweight utility package for common API-related tasks in Python, including JSON and Pydantic error translators that provide user-friendly Russian messages."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "dkurchigin", email = "kurchigin.dmitry@yandex.ru" },
|
|
7
7
|
]
|
|
8
8
|
dependencies = [
|
|
9
9
|
"pydantic>=2.12.3",
|
|
10
|
-
"psycopg2-binary>=2.9.10",
|
|
11
10
|
"email-validator>=2.3.0",
|
|
12
11
|
]
|
|
13
12
|
requires-python = ">=3.10"
|
|
@@ -152,16 +151,21 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
|
152
151
|
"FBT",
|
|
153
152
|
"SLF",
|
|
154
153
|
]
|
|
155
|
-
"src/various_api_tools/
|
|
154
|
+
"src/various_api_tools/validators/pydantic/constants.py" = [
|
|
156
155
|
"RUF001",
|
|
157
|
-
"TID252",
|
|
158
156
|
]
|
|
159
|
-
"src/various_api_tools/validators/pydantic.py" = [
|
|
157
|
+
"src/various_api_tools/validators/pydantic/utils.py" = [
|
|
160
158
|
"EM101",
|
|
161
|
-
"
|
|
159
|
+
"ANN401",
|
|
162
160
|
]
|
|
163
|
-
"src/various_api_tools/
|
|
164
|
-
"
|
|
161
|
+
"src/various_api_tools/translators/psycopg/base.py" = [
|
|
162
|
+
"ANN401",
|
|
163
|
+
]
|
|
164
|
+
"src/various_api_tools/translators/psycopg/psycopg.py" = [
|
|
165
|
+
"ANN401",
|
|
166
|
+
]
|
|
167
|
+
"src/various_api_tools/translators/psycopg/psycopg2.py" = [
|
|
168
|
+
"ANN401",
|
|
165
169
|
]
|
|
166
170
|
|
|
167
171
|
[tool.ruff.format]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""A package for various API utility tools.
|
|
2
|
+
|
|
3
|
+
Including JSON and Pydantic error translators.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .translators.json import JSONDecodeErrorTranslator
|
|
7
|
+
from .translators.psycopg.psycopg import PsycopgErrorTranslator
|
|
8
|
+
from .translators.psycopg.psycopg2 import Psycopg2ErrorTranslator
|
|
9
|
+
from .translators.pydantic import PydanticValidationErrorTranslator
|
|
10
|
+
from .validators.pydantic.constants import PYDANTIC_ERROR_TYPES
|
|
11
|
+
from .validators.pydantic.utils import (
|
|
12
|
+
email_validator,
|
|
13
|
+
latitude_validator,
|
|
14
|
+
longitude_validator,
|
|
15
|
+
optional_string_validator,
|
|
16
|
+
strip_validator,
|
|
17
|
+
validate_required_field,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = (
|
|
21
|
+
"PYDANTIC_ERROR_TYPES",
|
|
22
|
+
"JSONDecodeErrorTranslator",
|
|
23
|
+
"Psycopg2ErrorTranslator",
|
|
24
|
+
"PsycopgErrorTranslator",
|
|
25
|
+
"PydanticValidationErrorTranslator",
|
|
26
|
+
"email_validator",
|
|
27
|
+
"latitude_validator",
|
|
28
|
+
"longitude_validator",
|
|
29
|
+
"optional_string_validator",
|
|
30
|
+
"strip_validator",
|
|
31
|
+
"validate_required_field",
|
|
32
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Module for concrete translator for psycopg/psycopg2 errors."""
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Abstract base class for translating psycopg/psycopg2 database errors.
|
|
2
|
+
|
|
3
|
+
This module provides a foundation for parsing PostgreSQL error
|
|
4
|
+
details (such as `pgcode` and `pgerror`) from psycopg exceptions and converting
|
|
5
|
+
them into structured, human-readable error descriptions.
|
|
6
|
+
It supports both `psycopg2` and `psycopg3` by abstracting access to error attributes,
|
|
7
|
+
allowing concrete implementations to handle version-specific details.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .constants import (
|
|
15
|
+
CHECK_VIOLATION_PATTERN,
|
|
16
|
+
DEFAULT_CODE_MAP,
|
|
17
|
+
UNIQUE_VIOLATION_PATTERN,
|
|
18
|
+
UNKNOWN_CHECK_DESCRIPTION,
|
|
19
|
+
UNKNOWN_CODE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ErrorData:
|
|
25
|
+
"""Represents structured data extracted from a database error.
|
|
26
|
+
|
|
27
|
+
Used to capture constraint details such as the violated field and
|
|
28
|
+
its conflicting value.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
key: The name of the field or constraint involved in the error.
|
|
32
|
+
value: The value that caused the constraint violation.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
key: str
|
|
37
|
+
value: Any
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BasePsycopgTranslator:
|
|
41
|
+
"""Base translator for turning psycopg errors into user-friendly messages.
|
|
42
|
+
|
|
43
|
+
Supports customizing error messages via:
|
|
44
|
+
- `code_map`: maps SQLSTATE codes to human-readable messages
|
|
45
|
+
- `check_map`: maps check constraint names to meaningful descriptions
|
|
46
|
+
|
|
47
|
+
Subclasses must implement `_get_pgcode()` and `_get_pgerror()` to support specific
|
|
48
|
+
psycopg versions (e.g., psycopg2 vs. psycopg3).
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
code_map (dict): Mapping from SQLSTATE codes (str) to error messages (str).
|
|
52
|
+
check_map (dict): Mapping from check constraint names (str)
|
|
53
|
+
to descriptions (str).
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
code_map: dict
|
|
58
|
+
check_map: dict
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
*,
|
|
63
|
+
code_map: dict | None = None,
|
|
64
|
+
check_map: dict | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Initialize the translator with optional custom message mappings.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
code_map: Optional mapping from SQLSTATE codes (e.g. '23505')
|
|
70
|
+
to error messages. If None, defaults to `DEFAULT_CODE_MAP`.
|
|
71
|
+
check_map: Optional mapping from check constraint names
|
|
72
|
+
to descriptive messages. If None, defaults to an empty dict.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
```python
|
|
76
|
+
custom_codes = {"23505": "Значение уже существует"}
|
|
77
|
+
custom_checks = {"age_check": "Возраст должен быть от 18 до 120"}
|
|
78
|
+
t = BasePsycopgTranslator(code_map=custom_codes, check_map=custom_checks)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
if code_map is not None:
|
|
83
|
+
self.code_map = code_map
|
|
84
|
+
else:
|
|
85
|
+
self.code_map = DEFAULT_CODE_MAP
|
|
86
|
+
|
|
87
|
+
if check_map is not None:
|
|
88
|
+
self.check_map = check_map
|
|
89
|
+
else:
|
|
90
|
+
self.check_map = {}
|
|
91
|
+
|
|
92
|
+
def translate(
|
|
93
|
+
self,
|
|
94
|
+
error: Any,
|
|
95
|
+
) -> str:
|
|
96
|
+
"""Translate a psycopg database error into a user-friendly message.
|
|
97
|
+
|
|
98
|
+
Extracts `pgcode` and `pgerror` from the error and formats a meaningful response
|
|
99
|
+
based on the type of constraint violation (unique, check, foreign key).
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
error: A psycopg exception (e.g. IntegrityError, DatabaseError).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A formatted error message, or a fallback if no match is found.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
```python
|
|
109
|
+
# Given error: unique_violation on email = 'test@example.com'
|
|
110
|
+
print(translator.translate(error))
|
|
111
|
+
#> "БД уже содержит значение: ключ email, значение test@example.com"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
msg: str = UNKNOWN_CODE
|
|
116
|
+
|
|
117
|
+
pgcode = self._get_pgcode(error)
|
|
118
|
+
pgerror = self._get_pgerror(error)
|
|
119
|
+
|
|
120
|
+
if pgcode is not None:
|
|
121
|
+
code_msg: str | None = self.code_map.get(pgcode, UNKNOWN_CODE)
|
|
122
|
+
data: ErrorData | None = None
|
|
123
|
+
|
|
124
|
+
if pgcode == "23514":
|
|
125
|
+
data = self._translate_check_violation(error_msg=pgerror)
|
|
126
|
+
elif pgcode in ["23505", "23503"]:
|
|
127
|
+
data = self._translate_unique_violation(error_msg=pgerror)
|
|
128
|
+
|
|
129
|
+
if data and code_msg:
|
|
130
|
+
msg = f"{code_msg}: ключ {data.key}, значение {data.value}."
|
|
131
|
+
else:
|
|
132
|
+
msg = code_msg
|
|
133
|
+
|
|
134
|
+
return msg
|
|
135
|
+
|
|
136
|
+
def _get_pgcode(self, error: Any) -> str | None:
|
|
137
|
+
"""Extract pgcode code from the error.
|
|
138
|
+
|
|
139
|
+
Must be implemented by subclasses to support specific psycopg versions.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
error: The raw database exception.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
The SQLSTATE code as a string (e.g. '23505'), or None if not available.
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
def _get_pgerror(self, error: Any) -> str | None:
|
|
151
|
+
"""Extract the full error message from the exception.
|
|
152
|
+
|
|
153
|
+
Must be implemented by subclasses to support specific psycopg versions.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
error: The raw database exception.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Full error message string, or None if not available.
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
raise NotImplementedError
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def _translate_unique_violation(cls, error_msg: str) -> ErrorData:
|
|
166
|
+
"""Extract field and value from a unique constraint violation error.
|
|
167
|
+
|
|
168
|
+
Uses a regex to parse the PostgreSQL
|
|
169
|
+
"DETAIL: Key (field)=(value) already exists" message.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
error_msg: The full error text from the database.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
ErrorData with extracted field name and value, or None if not matched.
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
```python
|
|
179
|
+
error_msg = "DETAIL: Key (email)=(user@example.com) already exists."
|
|
180
|
+
print(BasePsycopgTranslator._translate_unique_violation(error_msg))
|
|
181
|
+
#> ErrorData(key='email', value='user@example.com')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
data: ErrorData | None = None
|
|
186
|
+
|
|
187
|
+
pattern = re.compile(UNIQUE_VIOLATION_PATTERN)
|
|
188
|
+
matched = pattern.search(error_msg)
|
|
189
|
+
|
|
190
|
+
if matched is not None:
|
|
191
|
+
data = ErrorData(key=matched.group(2), value=matched.group(3))
|
|
192
|
+
|
|
193
|
+
return data
|
|
194
|
+
|
|
195
|
+
def _translate_check_violation(self, error_msg: str) -> ErrorData | None:
|
|
196
|
+
"""Extract constraint name and description from a check constraint violation.
|
|
197
|
+
|
|
198
|
+
Looks up a human-readable description in `self.check_map`. If not found,
|
|
199
|
+
returns a default message.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
error_msg: The full error text from the database.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
ErrorData with constraint name and description, or None if not matched.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
```python
|
|
209
|
+
error_msg = "violates check constraint 'age_check'"
|
|
210
|
+
check_map = {"age_check": "Возраст должен быть от 18 до 120"}
|
|
211
|
+
translator = BasePsycopgTranslator(check_map=check_map)
|
|
212
|
+
print(translator._translate_check_violation(error_msg))
|
|
213
|
+
#> ErrorData(key='age_check', value='Возраст должен быть от 18 до 120')
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
data: ErrorData | None = None
|
|
218
|
+
|
|
219
|
+
pattern = re.compile(CHECK_VIOLATION_PATTERN)
|
|
220
|
+
matched = pattern.search(error_msg)
|
|
221
|
+
if matched is not None:
|
|
222
|
+
check_constraint = matched.group(1)
|
|
223
|
+
constraint_description = self.check_map.get(
|
|
224
|
+
check_constraint,
|
|
225
|
+
UNKNOWN_CHECK_DESCRIPTION,
|
|
226
|
+
)
|
|
227
|
+
data = ErrorData(key=check_constraint, value=constraint_description)
|
|
228
|
+
|
|
229
|
+
return data
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Psycopg error translation constants.
|
|
2
|
+
|
|
3
|
+
This module defines constants used by the PsycopgErrorTranslator for parsing and
|
|
4
|
+
translating PostgreSQL error messages from psycopg and psycopg2 into user-friendly
|
|
5
|
+
localized messages.
|
|
6
|
+
|
|
7
|
+
The constants include:
|
|
8
|
+
- Regular expressions to extract structured details from PostgreSQL error texts
|
|
9
|
+
- Mappings from SQLSTATE codes to human-readable messages
|
|
10
|
+
- Default error descriptions for common constraint violations
|
|
11
|
+
|
|
12
|
+
These are primarily used to enhance error reporting in API responses by providing
|
|
13
|
+
clear, actionable feedback based on database-level constraints (e.g., unique,
|
|
14
|
+
foreign key, and check violations).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Final
|
|
18
|
+
|
|
19
|
+
UNIQUE_VIOLATION_PATTERN: Final[str] = r"DETAIL:.*(Key|Ключ).*\((.*?)\)=\((.*?)\)"
|
|
20
|
+
"""Regex pattern to extract details from PostgreSQL unique constraint violation errors.
|
|
21
|
+
|
|
22
|
+
Matches the standard PostgreSQL error detail message format:
|
|
23
|
+
'DETAIL: Key (field)=(value) already exists.' or localized version 'Ключ'.
|
|
24
|
+
|
|
25
|
+
Captures:
|
|
26
|
+
Group 1: 'Key' or 'Ключ' (language-independent match)
|
|
27
|
+
Group 2: Column name involved in the constraint
|
|
28
|
+
Group 3: Conflicting value
|
|
29
|
+
|
|
30
|
+
Used by: `BasePsycopgTranslator._translate_unique_violation`
|
|
31
|
+
Example match: 'DETAIL: Key (email)=(user@example.com) already exists.' →
|
|
32
|
+
key='email', value='user@example.com'"""
|
|
33
|
+
|
|
34
|
+
CHECK_VIOLATION_PATTERN: Final[str] = r'violates check constraint.*?"(.*?)"'
|
|
35
|
+
"""Regex pattern to extract the name of a violated check constraint from PSQL error.
|
|
36
|
+
|
|
37
|
+
Matches error messages containing: 'violates check constraint "constraint_name"'.
|
|
38
|
+
Captures the constraint name in group 1.
|
|
39
|
+
|
|
40
|
+
Used by: `BasePsycopgTranslator._translate_check_violation`
|
|
41
|
+
Example: 'new row for relation "users" violates check constraint "users_age_check"'
|
|
42
|
+
→ 'users_age_check'"""
|
|
43
|
+
|
|
44
|
+
UNKNOWN_CHECK_DESCRIPTION: Final[str] = "Невалидная запись в БД"
|
|
45
|
+
"""Fallback message used when a check constraint is violated but no custom description
|
|
46
|
+
is configured.
|
|
47
|
+
|
|
48
|
+
Used as the default value when a constraint name is not found in `check_map`.
|
|
49
|
+
Displayed to users when the system cannot provide more specific validation feedback."""
|
|
50
|
+
|
|
51
|
+
DEFAULT_CODE_MAP: Final[dict[str, str]] = {
|
|
52
|
+
"23503": "Указан несуществующий идентификатор связанного объекта",
|
|
53
|
+
"23505": "БД уже содержит значение",
|
|
54
|
+
"23514": "Нарушено ограничение данных",
|
|
55
|
+
}
|
|
56
|
+
"""Default mapping of PostgreSQL SQLSTATE codes to user-friendly Russian error messages.
|
|
57
|
+
|
|
58
|
+
Each key is a standardized SQLSTATE code:
|
|
59
|
+
- "23503" (foreign_key_violation): Referenced object does not exist
|
|
60
|
+
- "23505" (unique_violation): Duplicate key value
|
|
61
|
+
- "23514" (check_violation): Data fails check constraint
|
|
62
|
+
|
|
63
|
+
Values are localized messages suitable for API responses.
|
|
64
|
+
Can be overridden in translator instances for custom messaging."""
|
|
65
|
+
|
|
66
|
+
UNKNOWN_CODE: Final[str] = "Неизвестная ошибка БД"
|
|
67
|
+
"""Fallback message used when an unrecognized SQLSTATE code is encountered.
|
|
68
|
+
|
|
69
|
+
Ensures that no raw database error codes or technical messages are exposed to end users.
|
|
70
|
+
Should be replaced with more specific messages during
|
|
71
|
+
system localization or customization."""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Translator implementation for psycopg3 (psycopg) database errors.
|
|
2
|
+
|
|
3
|
+
This module provides a concrete implementation of `BasePsycopgTranslator` tailored
|
|
4
|
+
for `psycopg` (aka psycopg3) exceptions.
|
|
5
|
+
It extracts error details using psycopg3's exception interface:
|
|
6
|
+
- `sqlstate`: the SQLSTATE code (e.g. '23505' for unique violation)
|
|
7
|
+
- `str(error)`: the full error message, as psycopg3 does not expose `pgerror` directly
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .base import BasePsycopgTranslator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PsycopgErrorTranslator(BasePsycopgTranslator):
|
|
16
|
+
"""Concrete error translator for psycopg (psycopg3) exceptions.
|
|
17
|
+
|
|
18
|
+
Implements the `_get_pgcode` and `_get_pgerror` methods to extract error details
|
|
19
|
+
from `psycopg` exception objects:
|
|
20
|
+
- Uses `.sqlstate` attribute for SQLSTATE code (psycopg3 equivalent of pgcode)
|
|
21
|
+
- Uses `str(error)` as the error message, since psycopg3 does not expose `.pgerror`
|
|
22
|
+
|
|
23
|
+
Supports customization via:
|
|
24
|
+
- `code_map`: mapping of SQLSTATE codes to user-facing messages
|
|
25
|
+
- `check_map`: mapping of check constraint names to human-readable descriptions
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
translator = PsycopgErrorTranslator(
|
|
29
|
+
code_map={"23505": "Значение уже существует"},
|
|
30
|
+
check_map={"users_age_check": "Возраст должен быть от 18 до 120"}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Note:
|
|
34
|
+
This class is intended for use with `psycopg` >= 3.0. For `psycopg2`, use
|
|
35
|
+
`Psycopg2ErrorTranslator` instead.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def _get_pgcode(self, error: Any) -> str | None:
|
|
40
|
+
return getattr(error, "sqlstate", None)
|
|
41
|
+
|
|
42
|
+
def _get_pgerror(self, error: Any) -> str | None:
|
|
43
|
+
return str(error)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Translator implementation for psycopg2-specific database errors.
|
|
2
|
+
|
|
3
|
+
This module provides a concrete implementation of `BasePsycopgTranslator`
|
|
4
|
+
tailored for `psycopg2`exceptions.
|
|
5
|
+
It extracts error details using `psycopg2`'s attribute-based interface:
|
|
6
|
+
- `pgcode`: the SQLSTATE code (e.g. '23505' for unique violation)
|
|
7
|
+
- `pgerror`: the full error message from PostgreSQL
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .base import BasePsycopgTranslator
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Psycopg2ErrorTranslator(BasePsycopgTranslator):
|
|
16
|
+
"""Concrete error translator for psycopg2 exceptions.
|
|
17
|
+
|
|
18
|
+
Implements the `_get_pgcode` and `_get_pgerror` methods to extract error details
|
|
19
|
+
from `psycopg2` exception objects using their standard attributes.
|
|
20
|
+
|
|
21
|
+
This class supports customization via:
|
|
22
|
+
- `code_map`: mapping of SQLSTATE codes to user-friendly messages
|
|
23
|
+
- `check_map`: mapping of check constraint names to descriptive texts
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
translator = Psycopg2ErrorTranslator(
|
|
27
|
+
code_map={"23505": "Значение уже существует"},
|
|
28
|
+
check_map={"users_age_check": "Возраст должен быть от 18 до 120"}
|
|
29
|
+
)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def _get_pgcode(self, error: Any) -> str | None:
|
|
33
|
+
return getattr(error, "pgcode", None)
|
|
34
|
+
|
|
35
|
+
def _get_pgerror(self, error: Any) -> str | None:
|
|
36
|
+
return getattr(error, "pgerror", None)
|
{various_api_tools-0.3.4 → various_api_tools-1.0.0}/src/various_api_tools/translators/pydantic.py
RENAMED
|
@@ -10,7 +10,10 @@ from typing import Final
|
|
|
10
10
|
|
|
11
11
|
from pydantic_core import ErrorDetails
|
|
12
12
|
|
|
13
|
-
from ..
|
|
13
|
+
from ..validators.pydantic.constants import ( # noqa: TID252
|
|
14
|
+
PYDANTIC_ERROR_TYPES,
|
|
15
|
+
UNKNOWN_ERROR_TYPE,
|
|
16
|
+
)
|
|
14
17
|
|
|
15
18
|
DEFAULT_LOCATION_PREFIX: Final[str] = "Поле"
|
|
16
19
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Module for concrete API validators with Pydantic."""
|
|
@@ -1,11 +1,40 @@
|
|
|
1
|
-
"""Module
|
|
1
|
+
"""Module: Pydantic Validator Constants.
|
|
2
2
|
|
|
3
|
-
This module
|
|
4
|
-
|
|
3
|
+
This module defines constants used across Pydantic-based
|
|
4
|
+
validators in the application.
|
|
5
|
+
It includes:
|
|
6
|
+
- Geographical coordinate boundaries (latitude and longitude limits)
|
|
7
|
+
- A centralized mapping of error codes to localized human-readable
|
|
8
|
+
messages (`PYDANTIC_ERROR_TYPES`)
|
|
9
|
+
- Default error message for unknown validation issues (`UNKNOWN_ERROR_TYPE`)
|
|
5
10
|
"""
|
|
6
11
|
|
|
7
12
|
from typing import Final
|
|
8
13
|
|
|
14
|
+
MIN_LATITUDE: Final[float] = -90.0
|
|
15
|
+
"""Minimum allowed latitude value in degrees (-90.0 corresponds to the South Pole).
|
|
16
|
+
|
|
17
|
+
Used in geographic validation to ensure latitude values are within valid range.
|
|
18
|
+
See: `latitude_validator` in `utils.py`."""
|
|
19
|
+
|
|
20
|
+
MAX_LATITUDE: Final[float] = 90.0
|
|
21
|
+
"""Maximum allowed latitude value in degrees (90.0 corresponds to the North Pole).
|
|
22
|
+
|
|
23
|
+
Used in geographic validation to ensure latitude values are within valid range.
|
|
24
|
+
See: `latitude_validator` in `utils.py`."""
|
|
25
|
+
|
|
26
|
+
MIN_LONGITUDE: Final[float] = -180.0
|
|
27
|
+
"""Minimum allowed longitude value in degrees (-180.0 corresponds to the IDL).
|
|
28
|
+
|
|
29
|
+
Used in geographic validation to ensure longitude values are within valid range.
|
|
30
|
+
See: `longitude_validator` in `utils.py`."""
|
|
31
|
+
|
|
32
|
+
MAX_LONGITUDE: Final[float] = 180.0
|
|
33
|
+
"""Maximum allowed longitude value in degrees (180.0 corresponds to the IDL).
|
|
34
|
+
|
|
35
|
+
Used in geographic validation to ensure longitude values are within valid range.
|
|
36
|
+
See: `longitude_validator` in `utils.py`."""
|
|
37
|
+
|
|
9
38
|
PYDANTIC_ERROR_TYPES: dict[str, str] = {
|
|
10
39
|
"missing": "Не заполнено обязательное поле",
|
|
11
40
|
"uuid_parsing": "Невалидное значение для UUID",
|
|
@@ -37,4 +66,22 @@ PYDANTIC_ERROR_TYPES: dict[str, str] = {
|
|
|
37
66
|
"incorrect_email": "Невалидное значение email-адреса",
|
|
38
67
|
"list_expected": "Некорректный тип данных. Ожидается список.",
|
|
39
68
|
}
|
|
69
|
+
"""Mapping of Pydantic built-in error codes to localized Russian messages.
|
|
70
|
+
|
|
71
|
+
Used by custom validators to provide consistent, user-friendly error descriptions
|
|
72
|
+
in API responses. Each key corresponds to a standard Pydantic error type.
|
|
73
|
+
Values are human-readable messages suitable for end users or frontend display.
|
|
74
|
+
|
|
75
|
+
Example keys:
|
|
76
|
+
- 'string_type': invalid type for string field
|
|
77
|
+
- 'incorrect_email': failed email format validation
|
|
78
|
+
- 'missing': required field is missing
|
|
79
|
+
|
|
80
|
+
Used in: `optional_string_validator`, `email_validator`, etc."""
|
|
81
|
+
|
|
40
82
|
UNKNOWN_ERROR_TYPE: Final[str] = "Неизвестная ошибка"
|
|
83
|
+
"""Default fallback message for validation errors with
|
|
84
|
+
no matching code in `PYDANTIC_ERROR_TYPES`.
|
|
85
|
+
|
|
86
|
+
Used when an unexpected or unrecognized error code is encountered during validation,
|
|
87
|
+
ensuring that no raw or technical messages are exposed to the user."""
|