1010
1111class ErrorCatalogMeta (type ):
1212
13- _registry : t .Dict [str , ExceptionWithCodeType ]
13+ __error_registry : t .Dict [str , ExceptionWithCodeType ]
14+ __subcatalog_registry : t .Dict [str , "ErrorCatalogMeta" ]
1415
1516 def __init__ (self , * args , ** kwargs ):
1617 super ().__init__ (* args , ** kwargs )
17- self ._registry = OrderedDict (
18+ self .__error_registry = OrderedDict (
1819 (v .code , v ) for _ , v in self .__dict__ .items () if is_error_class (v )
1920 )
21+ self .__subcatalog_registry = OrderedDict (
22+ (v .__name__ , v ) for _ , v in self .__dict__ .items () if isinstance (v , ErrorCatalogMeta )
23+ )
2024
2125 def __str__ (self ) -> str :
2226 return self .__name__
@@ -26,42 +30,69 @@ def __repr__(self) -> str:
2630
2731 def __iter__ (self ) -> t .Iterator [ExceptionWithCodeType ]:
2832 """Iterate over registered errors."""
29- yield from self ._registry .values ()
33+ yield from self .__error_registry .values ()
34+ yield from (e for c in self .__subcatalog_registry .values () for e in c )
3035
3136 def __len__ (self ) -> int :
32- return len (self ._registry )
37+ return len (self .all () )
3338
3439 def __contains__ (self , item : ExceptionWithCodeType ) -> bool :
35- return item in self ._registry . values ()
40+ return item in self .all ()
3641
3742 def add_instance (self , error_class : ExceptionWithCodeType ) -> None :
3843 """Registers an ExceptionWithCode subtype as an element of the ErrorCatalog."""
39- self ._registry [error_class .code ] = error_class
44+ self .__error_registry [error_class .code ] = error_class
4045 setattr (self , error_class .code , error_class )
4146 error_class .catalog = t .cast ("ErrorCatalog" , self )
4247
4348 def all (self ) -> t .Tuple [ExceptionWithCodeType , ...]:
44- return tuple (self ._registry .values ())
49+ return tuple (self .__iter__ ())
50+
51+ def subcatalogs (self ) -> t .Tuple ["ErrorCatalogMeta" , ...]:
52+ return tuple (self .__subcatalog_registry .values ())
4553
4654
4755class ErrorCatalog (metaclass = ErrorCatalogMeta ):
4856 """
4957 A class that can serve as a collection of named BaseErrors, gathered with a common reason.
5058 Instances of BaseErrors are meant to be declared as fields. Names of their fields may be
51- used as default value of `code` for each instance. The catalog may set default value of
52- `area` for all of them.
59+ used as default value of `code` for each instance.
60+
61+ >>> class SomeCatalog(ErrorCatalog):
62+ ... SomeError = error_builder()
63+ ... AnotherError = error_builder("ExpliciteNameForAnotherError")
64+
5365
5466 Developers are encouraged to gather errors of their business logic into such error classes.
67+ Such catalogs can be nested to structurize their relation further, in a form of a tree.
68+
69+ >>> class ExternalCatalog(ErrorCatalog):
70+ ... ExternalError = error_builder()
71+
72+ >>> class CompositeCatalog(ErrorCatalog):
73+ ... OwnError = error_builder()
74+ ... ExternalCatalogIncluded = ExternalCatalog
75+ ...
76+ ... class NestedCatalog(ErrorCatalog):
77+ ... NestedError = error_builder()
78+
79+ >>> assert CompositeCatalog.all() == (
80+ ... CompositeCatalog.OwnError,
81+ ... ExternalCatalog.ExternalError,
82+ ... CompositeCatalog.NestedCatalog.NestedError
83+ ... )
84+
85+
5586 If you want to reuse an error already attached to a catalog, use error's `clone` method
5687 like this:
5788
5889 >>> class OldCatalog(ErrorCatalog):
59- ... ERROR = ExceptionWithCode ()
90+ ... OldError = error_builder ()
6091
6192 >>> class NewCatalog(ErrorCatalog):
62- ... AN_EXISTING_ERROR = OldCatalog.ERROR .clone()
93+ ... AnExistingError = OldCatalog.OldError .clone()
6394
64- >>> assert OldCatalog.ERROR == NewCatalog.AN_EXISTING_ERROR
65- >>> assert OldCatalog.ERROR .catalog == OldCatalog
66- >>> assert NewCatalog.AN_EXISTING_ERROR .catalog == NewCatalog
95+ >>> assert OldCatalog.OldCatalog is not NewCatalog.AnExistingError
96+ >>> assert OldCatalog.OldCatalog .catalog == OldCatalog
97+ >>> assert NewCatalog.AnExistingError .catalog == NewCatalog
6798 """
0 commit comments