2323import dataclasses
2424import logging
2525import os
26- from typing import Any , Dict , List , MutableMapping
26+ from typing import Any , List , MutableMapping
2727
28- from pydantic . class_validators import validator
28+ from pydantic import field_validator , ValidationError
2929from pydantic .dataclasses import dataclass
30- from pydantic . error_wrappers import ValidationError
30+ from pydantic_core import ErrorDetails
3131
3232import toml
3333
4040
4141
4242class InvalidConfigurationError (Exception ):
43- def __init__ (self , errors : List [Dict [ str , Any ] ]) -> None :
43+ def __init__ (self , errors : List [ErrorDetails ]) -> None :
4444 self .errors = errors
4545
4646 def __str__ (self ) -> str :
4747 msg = ['Invalid configuration:' ]
4848
4949 for e in self .errors :
50- loc = e [ 'loc' ]
51- msg .append (f"* { '.' . join ( loc ) } : { e ['msg' ]} " )
50+ loc = self . _loc_str ( e )
51+ msg .append (f"* { loc } : { e ['msg' ]} " )
5252
5353 return '\n ' .join (msg )
5454
55+ # The loc field in ErrorDetails is a tuple of strings and ints.
56+ # https://docs.pydantic.dev/latest/errors/errors/#customize-error-messages
57+ @staticmethod
58+ def _loc_str (details : ErrorDetails ) -> str :
59+ path = ''
60+ for i , element in enumerate (details ['loc' ]):
61+ if isinstance (element , str ):
62+ if i > 0 :
63+ path += '.'
64+ path += element
65+ else : # pragma: no cover (our config does not use lists)
66+ path += f'[{ element } ]'
67+ return path
68+
5569
5670class NoSuchConfigurationError (AttributeError ):
5771 pass
@@ -66,7 +80,7 @@ def __getattr__(self, name: str) -> Any:
6680class Main (_Base ):
6781 verbose : bool = False
6882
69- @validator ('verbose' , pre = True )
83+ @field_validator ('verbose' , mode = 'before' )
7084 def verbose_is_boolean (cls , value : Any ) -> bool :
7185 return is_boolean (value )
7286
@@ -78,11 +92,11 @@ class Redis(_Base):
7892 password : str = DEFAULT_PASSWORD
7993 ssl : bool = False
8094
81- @validator ('host' , pre = True )
95+ @field_validator ('host' , mode = 'before' )
8296 def host_is_non_empty_string (cls , value : Any ) -> str :
8397 return is_non_empty_string (value )
8498
85- @validator ('port' , pre = True )
99+ @field_validator ('port' , mode = 'before' )
86100 def port_is_strictly_positive_integer (cls , value : Any ) -> int :
87101 return is_strictly_positive_integer (value )
88102
@@ -92,7 +106,7 @@ class Config(_Base):
92106 main : Main = dataclasses .field (default_factory = Main )
93107 redis : Redis = dataclasses .field (default_factory = Redis )
94108
95- def __post_init_post_parse__ (self ) -> None :
109+ def __post_init__ (self ) -> None :
96110 self .warn_about_default_passwords ()
97111
98112 @classmethod
0 commit comments