various-api-tools 0.3.6__tar.gz → 2.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.
Files changed (29) hide show
  1. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/PKG-INFO +5 -5
  2. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/README.md +4 -4
  3. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/pyproject.toml +13 -7
  4. various_api_tools-2.0.0/src/various_api_tools/__init__.py +32 -0
  5. various_api_tools-2.0.0/src/various_api_tools/translators/psycopg/__init__.py +1 -0
  6. various_api_tools-2.0.0/src/various_api_tools/translators/psycopg/base.py +256 -0
  7. various_api_tools-2.0.0/src/various_api_tools/translators/psycopg/constants.py +88 -0
  8. various_api_tools-2.0.0/src/various_api_tools/translators/psycopg/psycopg.py +43 -0
  9. various_api_tools-2.0.0/src/various_api_tools/translators/psycopg/psycopg2.py +36 -0
  10. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/src/various_api_tools/translators/pydantic.py +4 -1
  11. various_api_tools-2.0.0/src/various_api_tools/validators/pydantic/__init__.py +1 -0
  12. various_api_tools-0.3.6/src/various_api_tools/error_maps/pydantic.py → various_api_tools-2.0.0/src/various_api_tools/validators/pydantic/constants.py +50 -3
  13. various_api_tools-2.0.0/src/various_api_tools/validators/pydantic/utils.py +254 -0
  14. various_api_tools-2.0.0/tests/translators/test_psycopg.py +110 -0
  15. various_api_tools-2.0.0/tests/translators/test_psycopg2.py +104 -0
  16. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/tests/validators/test_pydantic.py +31 -15
  17. various_api_tools-0.3.6/src/various_api_tools/__init__.py +0 -18
  18. various_api_tools-0.3.6/src/various_api_tools/error_maps/__init__.py +0 -1
  19. various_api_tools-0.3.6/src/various_api_tools/translators/psycopg2.py +0 -165
  20. various_api_tools-0.3.6/src/various_api_tools/validators/pydantic.py +0 -221
  21. various_api_tools-0.3.6/tests/translators/test_psycopg2.py +0 -97
  22. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/src/various_api_tools/translators/__init__.py +0 -0
  23. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/src/various_api_tools/translators/json.py +0 -0
  24. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/src/various_api_tools/validators/__init__.py +0 -0
  25. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/tests/__init__.py +0 -0
  26. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/tests/translators/__init__.py +0 -0
  27. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/tests/translators/test_json.py +0 -0
  28. {various_api_tools-0.3.6 → various_api_tools-2.0.0}/tests/translators/test_pydantic.py +0 -0
  29. {various_api_tools-0.3.6 → various_api_tools-2.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.6
3
+ Version: 2.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
@@ -28,12 +28,12 @@ Description-Content-Type: text/markdown
28
28
 
29
29
  ```python
30
30
  import json
31
- from various_api_tools.translators.json import DecodeErrorTranslator
31
+ from various_api_tools.translators.json import JSONDecodeErrorTranslator
32
32
 
33
33
  try:
34
34
  json.loads('{"name": "Alice",}')
35
35
  except json.JSONDecodeError as e:
36
- print(DecodeErrorTranslator.translate(e))
36
+ print(JSONDecodeErrorTranslator.translate(e))
37
37
 
38
38
  # Output:
39
39
  # Ошибка конвертации в формате JSON.
@@ -43,7 +43,7 @@ except json.JSONDecodeError as e:
43
43
 
44
44
  ```python
45
45
  from pydantic import BaseModel, ValidationError
46
- from various_api_tools.translators.pydantic import ValidationErrorTranslator
46
+ from various_api_tools.translators.pydantic import PydanticValidationErrorTranslator
47
47
 
48
48
  class User(BaseModel):
49
49
  email: str
@@ -51,7 +51,7 @@ class User(BaseModel):
51
51
  try:
52
52
  User(email=123)
53
53
  except ValidationError as e:
54
- print(ValidationErrorTranslator.translate(e.errors()))
54
+ print(PydanticValidationErrorTranslator.translate(e.errors()))
55
55
 
56
56
  # Output:
57
57
  # Поле: "email". Ошибка: "Невалидное строковое значение(str)";
@@ -6,12 +6,12 @@
6
6
 
7
7
  ```python
8
8
  import json
9
- from various_api_tools.translators.json import DecodeErrorTranslator
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(DecodeErrorTranslator.translate(e))
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 ValidationErrorTranslator
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(ValidationErrorTranslator.translate(e.errors()))
32
+ print(PydanticValidationErrorTranslator.translate(e.errors()))
33
33
 
34
34
  # Output:
35
35
  # Поле: "email". Ошибка: "Невалидное строковое значение(str)";
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "various-api-tools"
3
- version = "0.3.6"
3
+ version = "2.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" },
@@ -151,16 +151,22 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
151
151
  "FBT",
152
152
  "SLF",
153
153
  ]
154
- "src/various_api_tools/translators/pydantic.py" = [
154
+ "src/various_api_tools/validators/pydantic/constants.py" = [
155
155
  "RUF001",
156
- "TID252",
157
156
  ]
158
- "src/various_api_tools/validators/pydantic.py" = [
157
+ "src/various_api_tools/validators/pydantic/utils.py" = [
159
158
  "EM101",
160
- "TID252",
159
+ "ANN401",
161
160
  ]
162
- "src/various_api_tools/error_maps/pydantic.py" = [
163
- "RUF001",
161
+ "src/various_api_tools/translators/psycopg/base.py" = [
162
+ "ANN401",
163
+ "RUF012",
164
+ ]
165
+ "src/various_api_tools/translators/psycopg/psycopg.py" = [
166
+ "ANN401",
167
+ ]
168
+ "src/various_api_tools/translators/psycopg/psycopg2.py" = [
169
+ "ANN401",
164
170
  ]
165
171
 
166
172
  [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,256 @@
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 typing import Any
12
+
13
+ from .constants import (
14
+ CHECK_VIOLATION_PATTERN,
15
+ DEFAULT_CODE_MAP,
16
+ UNIQUE_VIOLATION_PATTERN,
17
+ UNKNOWN_CHECK_DESCRIPTION,
18
+ UNKNOWN_CODE,
19
+ UNKNOWN_USER_RAISE_DESCRIPTION,
20
+ )
21
+
22
+
23
+ class BasePsycopgTranslator:
24
+ """Base translator for turning psycopg errors into user-friendly messages.
25
+
26
+ Supports customization via:
27
+ - `code_map`: maps SQLSTATE codes to human-readable messages
28
+ - `check_map`: maps check constraint names to meaningful descriptions
29
+ - `user_map`: maps database column names to user-friendly field names
30
+
31
+ Subclasses must implement `_get_pgcode()` and `_get_pgerror()` to support specific
32
+ psycopg versions (e.g., psycopg2 vs. psycopg3).
33
+
34
+ Attributes:
35
+ code_map (dict): Mapping from SQLSTATE codes (str) to error messages (str).
36
+ check_map (dict): Mapping from check constraint names (str)
37
+ to descriptions (str).
38
+ user_map (dict): Mapping from DB column/field names (str)
39
+ to user-friendly names (str).
40
+
41
+ """
42
+
43
+ code_map: dict
44
+ check_map: dict
45
+ user_map: dict
46
+
47
+ check_codes: list[str] = ["23514"]
48
+ unique_codes: list[str] = ["23505", "23503"]
49
+ user_codes: list[str] = ["P0001"]
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ code_map: dict | None = None,
55
+ check_map: dict | None = None,
56
+ user_map: dict | None = None,
57
+ ) -> None:
58
+ """Initialize the translator with optional custom message mappings.
59
+
60
+ Args:
61
+ code_map: Optional mapping from SQLSTATE codes (e.g. '23505') to
62
+ error messages. If None, defaults to `DEFAULT_CODE_MAP`.
63
+ check_map: Optional mapping from check constraint names to
64
+ descriptive messages. If None, defaults to an empty dict.
65
+ user_map: Optional mapping from database column names to
66
+ user-friendly field names.
67
+ Used to improve readability in error messages.
68
+ Example: {"email": "Email пользователя", "phone": "Номер телефона"}
69
+ If None, defaults to an empty dict.
70
+
71
+ Example:
72
+ ```python
73
+ custom_codes = {"23505": "Значение уже существует"}
74
+ custom_checks = {"age_check": "Возраст должен быть от 18 до 120"}
75
+ t = BasePsycopgTranslator(code_map=custom_codes, check_map=custom_checks)
76
+ ```
77
+
78
+ """
79
+ self.code_map = code_map if code_map is not None else DEFAULT_CODE_MAP
80
+ self.check_map = check_map if check_map is not None else {}
81
+ self.user_map = user_map if user_map is not None else {}
82
+
83
+ def translate(
84
+ self,
85
+ error: Any,
86
+ ) -> str:
87
+ """Translate a psycopg database error into a user-friendly message.
88
+
89
+ Extracts `pgcode` and `pgerror` from the error and formats a meaningful response
90
+ based on the type of constraint violation (unique, check, foreign key).
91
+
92
+ Args:
93
+ error: A psycopg exception (e.g. IntegrityError, DatabaseError).
94
+
95
+ Returns:
96
+ A formatted error message, or a fallback if no match is found.
97
+
98
+ Example:
99
+ ```python
100
+ # Given error: unique_violation on email = 'test@example.com'
101
+ print(translator.translate(error))
102
+ #> "БД уже содержит значение: ключ email, значение test@example.com"
103
+ ```
104
+
105
+ """
106
+ msg: str = UNKNOWN_CODE
107
+
108
+ pgcode = self._get_pgcode(error)
109
+ pgerror = self._get_pgerror(error)
110
+
111
+ if pgcode is not None:
112
+ code_msg: str | None = self.code_map.get(pgcode, UNKNOWN_CODE)
113
+
114
+ if pgcode in self.check_codes:
115
+ msg = self._translate_check_violation(
116
+ code_msg=code_msg,
117
+ error_msg=pgerror,
118
+ )
119
+ elif pgcode in self.unique_codes:
120
+ msg = self._translate_unique_violation(
121
+ code_msg=code_msg,
122
+ error_msg=pgerror,
123
+ )
124
+ elif pgcode in self.user_codes:
125
+ msg = self._translate_user_raise(code_msg=code_msg, error_msg=pgerror)
126
+
127
+ return msg
128
+
129
+ def _get_pgcode(self, error: Any) -> str | None:
130
+ """Extract pgcode code from the error.
131
+
132
+ Must be implemented by subclasses to support specific psycopg versions.
133
+
134
+ Args:
135
+ error: The raw database exception.
136
+
137
+ Returns:
138
+ The SQLSTATE code as a string (e.g. '23505'), or None if not available.
139
+
140
+ """
141
+ raise NotImplementedError
142
+
143
+ def _get_pgerror(self, error: Any) -> str | None:
144
+ """Extract the full error message from the exception.
145
+
146
+ Must be implemented by subclasses to support specific psycopg versions.
147
+
148
+ Args:
149
+ error: The raw database exception.
150
+
151
+ Returns:
152
+ Full error message string, or None if not available.
153
+
154
+ """
155
+ raise NotImplementedError
156
+
157
+ @classmethod
158
+ def _translate_unique_violation(cls, code_msg: str, error_msg: str) -> str:
159
+ """Extract details from a unique constraint violation and format a message.
160
+
161
+ Parses the PostgreSQL error message to extract the violated field and
162
+ its conflicting value using a predefined regex pattern.
163
+
164
+ Args:
165
+ code_msg: Base message from code_map for this error type.
166
+ error_msg: Full PostgreSQL error text.
167
+
168
+ Returns:
169
+ Formatted message with field name and value if found;
170
+ otherwise returns the base code_msg.
171
+
172
+ Example:
173
+ For error_msg = "DETAIL: Key (email)=(user@example.com) already exists",
174
+ returns: "БД уже содержит значение: ключ email, значение user@example.com"
175
+
176
+ """
177
+ msg = code_msg
178
+
179
+ pattern = re.compile(UNIQUE_VIOLATION_PATTERN)
180
+ matched = pattern.search(error_msg)
181
+
182
+ if matched is not None:
183
+ constraint = matched.group(2)
184
+ description = matched.group(3)
185
+ if constraint and description:
186
+ msg = f"{code_msg}: ключ {constraint}, значение {description}"
187
+
188
+ return msg
189
+
190
+ def _translate_check_violation(self, code_msg: str, error_msg: str) -> str:
191
+ """Extract details from a check constraint violation and return a message.
192
+
193
+ Looks up the violated constraint in `check_map` for a custom description.
194
+ Falls back to `UNKNOWN_CHECK_DESCRIPTION` if not found.
195
+
196
+ Args:
197
+ code_msg: Base message from code_map for this error type.
198
+ error_msg: Full PostgreSQL error text.
199
+
200
+ Returns:
201
+ Formatted message with constraint name and its description if available;
202
+ otherwise returns the base code_msg.
203
+
204
+ Example:
205
+ If constraint 'age_check' is violated and check_map provides a description,
206
+ returns: "Нарушено ограничение данных: ключ age_check.
207
+ Описание: Возраст должен быть от 18 до 120"
208
+
209
+ """
210
+ msg = code_msg
211
+
212
+ pattern = re.compile(CHECK_VIOLATION_PATTERN)
213
+ matched = pattern.search(error_msg)
214
+ if matched is not None:
215
+ constraint = matched.group(1)
216
+ description = self.check_map.get(
217
+ constraint,
218
+ UNKNOWN_CHECK_DESCRIPTION,
219
+ )
220
+
221
+ if constraint and description:
222
+ msg = f"{code_msg}: ключ {constraint}. Описание: {description}"
223
+
224
+ return msg
225
+
226
+ def _translate_user_raise(self, code_msg: str, error_msg: str) -> str:
227
+ """Extract details from a user-raised exception and return a message.
228
+
229
+ Searches the error message for known field keys defined in `user_map`.
230
+ Returns a user-friendly description if a match is found; otherwise falls back
231
+ to `UNKNOWN_USER_RAISE_DESCRIPTION`.
232
+
233
+ Args:
234
+ code_msg: Base message from code_map for this error type.
235
+ error_msg: Full PostgreSQL error text from RAISE EXCEPTION.
236
+
237
+ Returns:
238
+ Formatted message with a friendly field description if found;
239
+ otherwise returns the base code_msg with a generic description.
240
+
241
+ Example:
242
+ If error_msg contains 'status' and user_map={"status": "Статус"},
243
+ returns: "Ошибка БД: Статус"
244
+
245
+ If no match is found:
246
+ returns: "Ошибка БД: Невозможно выполнить операцию"
247
+
248
+ """
249
+ description = UNKNOWN_USER_RAISE_DESCRIPTION
250
+
251
+ for key in self.user_map:
252
+ if key in error_msg:
253
+ description = self.user_map.get(key)
254
+ break
255
+
256
+ return f"{code_msg}: {description}"
@@ -0,0 +1,88 @@
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
+ UNKNOWN_USER_RAISE_DESCRIPTION: Final[str] = "Невозможно выполнить операцию"
52
+ """Default message shown when a custom database RAISE EXCEPTION is caught,
53
+ but no matching field or rule is found in `user_map`.
54
+
55
+ Used as a fallback in `BasePsycopgTranslator._translate_user_raise`
56
+ to avoid exposing raw internal messages to end users."""
57
+
58
+ USER_RAISE_KEY: Final[str] = "Ошибка БД"
59
+ """Default display key used in error messages generated from RAISE EXCEPTION.
60
+
61
+ Used as a generic identifier when formatting user-facing errors from custom
62
+ database raises, especially when no specific field mapping is available.
63
+
64
+ Example: '{Ошибка БД}: {описание}'"""
65
+
66
+ DEFAULT_CODE_MAP: Final[dict[str, str]] = {
67
+ "P0001": "Ошибка БД",
68
+ "23503": "Указан несуществующий идентификатор связанного объекта",
69
+ "23505": "БД уже содержит значение",
70
+ "23514": "Нарушено ограничение данных",
71
+ }
72
+ """Default mapping of PostgreSQL SQLSTATE codes to user-friendly Russian error messages.
73
+
74
+ Each key is a standardized SQLSTATE code:
75
+ - "P0001" (raise_exception): Custom exception raised in PL/pgSQL
76
+ - "23503" (foreign_key_violation): Referenced object does not exist
77
+ - "23505" (unique_violation): Duplicate key value
78
+ - "23514" (check_violation): Data fails check constraint
79
+
80
+ Values are localized messages suitable for API responses.
81
+ Can be overridden in translator instances for custom messaging."""
82
+
83
+ UNKNOWN_CODE: Final[str] = "Неизвестная ошибка БД"
84
+ """Fallback message used when an unrecognized SQLSTATE code is encountered.
85
+
86
+ Ensures that no raw database error codes or technical messages are exposed to end users.
87
+ Should be replaced with more specific messages during
88
+ 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)
@@ -10,7 +10,10 @@ from typing import Final
10
10
 
11
11
  from pydantic_core import ErrorDetails
12
12
 
13
- from ..error_maps.pydantic import PYDANTIC_ERROR_TYPES, UNKNOWN_ERROR_TYPE
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 for mapping Pydantic validation error codes to messages in Russian.
1
+ """Module: Pydantic Validator Constants.
2
2
 
3
- This module provides a dictionary that maps common Pydantic error types to descriptive
4
- error messages in Russian, used for user-friendly validation feedback.
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."""