3
3
import logging
4
4
import os
5
5
import re
6
+ import typing
6
7
from contextlib import contextmanager
7
8
from textwrap import indent , wrap
8
- from typing import Any , Dict , Iterator , List , Optional , Sequence , Union , cast
9
+ from typing import Any , Dict , Generator , Iterator , List , Optional , Sequence , Union , cast
9
10
10
11
from .fastjsonschema_exceptions import JsonSchemaValueException
11
12
13
+ if typing .TYPE_CHECKING :
14
+ import sys
15
+
16
+ if sys .version_info < (3 , 11 ):
17
+ from typing_extensions import Self
18
+ else :
19
+ from typing import Self
20
+
12
21
_logger = logging .getLogger (__name__ )
13
22
14
23
_MESSAGE_REPLACEMENTS = {
36
45
"property names" : "keys" ,
37
46
}
38
47
48
+ _FORMATS_HELP = """
49
+ For more details about `format` see
50
+ https://validate-pyproject.readthedocs.io/en/latest/api/validate_pyproject.formats.html
51
+ """
52
+
39
53
40
54
class ValidationError (JsonSchemaValueException ):
41
55
"""Report violations of a given JSON schema.
@@ -59,7 +73,7 @@ class ValidationError(JsonSchemaValueException):
59
73
_original_message = ""
60
74
61
75
@classmethod
62
- def _from_jsonschema (cls , ex : JsonSchemaValueException ):
76
+ def _from_jsonschema (cls , ex : JsonSchemaValueException ) -> "Self" :
63
77
formatter = _ErrorFormatting (ex )
64
78
obj = cls (str (formatter ), ex .value , formatter .name , ex .definition , ex .rule )
65
79
debug_code = os .getenv ("JSONSCHEMA_DEBUG_CODE_GENERATION" , "false" ).lower ()
@@ -72,7 +86,7 @@ def _from_jsonschema(cls, ex: JsonSchemaValueException):
72
86
73
87
74
88
@contextmanager
75
- def detailed_errors ():
89
+ def detailed_errors () -> Generator [ None , None , None ] :
76
90
try :
77
91
yield
78
92
except JsonSchemaValueException as ex :
@@ -83,7 +97,7 @@ class _ErrorFormatting:
83
97
def __init__ (self , ex : JsonSchemaValueException ):
84
98
self .ex = ex
85
99
self .name = f"`{ self ._simplify_name (ex .name )} `"
86
- self ._original_message = self .ex .message .replace (ex .name , self .name )
100
+ self ._original_message : str = self .ex .message .replace (ex .name , self .name )
87
101
self ._summary = ""
88
102
self ._details = ""
89
103
@@ -107,11 +121,12 @@ def details(self) -> str:
107
121
108
122
return self ._details
109
123
110
- def _simplify_name (self , name ):
124
+ @staticmethod
125
+ def _simplify_name (name : str ) -> str :
111
126
x = len ("data." )
112
127
return name [x :] if name .startswith ("data." ) else name
113
128
114
- def _expand_summary (self ):
129
+ def _expand_summary (self ) -> str :
115
130
msg = self ._original_message
116
131
117
132
for bad , repl in _MESSAGE_REPLACEMENTS .items ():
@@ -129,8 +144,9 @@ def _expand_summary(self):
129
144
130
145
def _expand_details (self ) -> str :
131
146
optional = []
132
- desc_lines = self .ex .definition .pop ("$$description" , [])
133
- desc = self .ex .definition .pop ("description" , None ) or " " .join (desc_lines )
147
+ definition = self .ex .definition or {}
148
+ desc_lines = definition .pop ("$$description" , [])
149
+ desc = definition .pop ("description" , None ) or " " .join (desc_lines )
134
150
if desc :
135
151
description = "\n " .join (
136
152
wrap (
@@ -142,18 +158,20 @@ def _expand_details(self) -> str:
142
158
)
143
159
)
144
160
optional .append (f"DESCRIPTION:\n { description } " )
145
- schema = json .dumps (self . ex . definition , indent = 4 )
161
+ schema = json .dumps (definition , indent = 4 )
146
162
value = json .dumps (self .ex .value , indent = 4 )
147
163
defaults = [
148
164
f"GIVEN VALUE:\n { indent (value , ' ' )} " ,
149
165
f"OFFENDING RULE: { self .ex .rule !r} " ,
150
166
f"DEFINITION:\n { indent (schema , ' ' )} " ,
151
167
]
152
- return "\n \n " .join (optional + defaults )
168
+ msg = "\n \n " .join (optional + defaults )
169
+ epilog = f"\n { _FORMATS_HELP } " if "format" in msg .lower () else ""
170
+ return msg + epilog
153
171
154
172
155
173
class _SummaryWriter :
156
- _IGNORE = { "description" , "default" , "title" , "examples" }
174
+ _IGNORE = frozenset (( "description" , "default" , "title" , "examples" ))
157
175
158
176
def __init__ (self , jargon : Optional [Dict [str , str ]] = None ):
159
177
self .jargon : Dict [str , str ] = jargon or {}
@@ -242,7 +260,9 @@ def _is_unecessary(self, path: Sequence[str]) -> bool:
242
260
key = path [- 1 ]
243
261
return any (key .startswith (k ) for k in "$_" ) or key in self ._IGNORE
244
262
245
- def _filter_unecessary (self , schema : dict , path : Sequence [str ]):
263
+ def _filter_unecessary (
264
+ self , schema : Dict [str , Any ], path : Sequence [str ]
265
+ ) -> Dict [str , Any ]:
246
266
return {
247
267
key : value
248
268
for key , value in schema .items ()
@@ -271,7 +291,7 @@ def _handle_list(
271
291
self (v , item_prefix , _path = [* path , f"[{ i } ]" ]) for i , v in enumerate (schemas )
272
292
)
273
293
274
- def _is_property (self , path : Sequence [str ]):
294
+ def _is_property (self , path : Sequence [str ]) -> bool :
275
295
"""Check if the given path can correspond to an arbitrarily named property"""
276
296
counter = 0
277
297
for key in path [- 2 ::- 1 ]:
0 commit comments