2828 resolve_list:
2929 .*Hello.*: Greeting
3030
31+ For YAML compliance, it is possible to declare the resolve list as follows
32+ to maintain ordering when using the configuration file with different programs.
33+ Both styles will be supported in future; however, this one is recommended for clarity and YAML compliance.
34+
35+ .. code-block:: yaml
36+ :linenos:
37+ :caption: Example
38+
39+ filter: to_resolve
40+ generic_resolver:
41+ field_mapping:
42+ to_resolve: resolved
43+ resolve_list:
44+ - .*Hello.*: Greeting
45+ - .*error.*: Error
46+ - never_match: Panic
47+
3148Alternatively, a YML file with a resolve list and a regex pattern can be used to resolve values.
3249For this, a field :code:`resolve_from_file` with the subfields :code:`path` and :code:`pattern`
3350must be added.
117134"""
118135
119136import re
120- from functools import cached_property
137+ import typing
138+ from functools import cached_property , partial
121139from pathlib import Path
122- from typing import List , Optional , Tuple , Union
123140
124141from attrs import define , field , validators
125142
126143from logprep .factory_error import InvalidConfigurationError
127144from logprep .processor .field_manager .rule import FieldManagerRule
145+ from logprep .util .converters import (
146+ convert_ordered_mapping_or_keep_mapping ,
147+ )
128148from logprep .util .getter import GetterFactory , RefreshableGetter
149+ from logprep .util .helper import FieldValue
129150
130151
131152class GenericResolverRule (FieldManagerRule ):
@@ -145,15 +166,22 @@ class Config(FieldManagerRule.Config):
145166 ]
146167 )
147168 """Mapping in form of :code:`{SOURCE_FIELD: DESTINATION_FIELD}`"""
148- resolve_list : dict = field (validator = (validators .instance_of (dict )), factory = dict )
169+ resolve_list : dict [str , FieldValue ] = field (
170+ validator = validators .deep_mapping (
171+ key_validator = validators .instance_of (str ),
172+ mapping_validator = validators .instance_of (dict ),
173+ ),
174+ converter = convert_ordered_mapping_or_keep_mapping ,
175+ factory = dict ,
176+ )
149177 """lookup mapping in form of
150178 :code:`{REGEX_PATTERN_0: ADDED_VALUE_0, ..., REGEX_PATTERN_N: ADDED_VALUE_N}`"""
151179 resolve_from_file : dict = field (
152180 validator = [
153181 validators .instance_of (dict ),
154182 validators .deep_mapping (
155183 key_validator = validators .in_ (["path" , "pattern" ]),
156- value_validator = validators .instance_of (Union [ str , int ] ),
184+ value_validator = validators .instance_of (( str , int ) ),
157185 ),
158186 ],
159187 factory = dict ,
@@ -178,38 +206,39 @@ class Config(FieldManagerRule.Config):
178206 authenticity and integrity of the loaded values.
179207
180208 """
181- ignore_case : Optional [ str ] = field (validator = validators .instance_of (bool ), default = False )
209+ ignore_case : bool = field (validator = validators .instance_of (bool ), default = False )
182210 """(Optional) Ignore case when matching resolve values. Defaults to :code:`False`."""
183211
184212 additions : dict = field (default = {}, eq = False , init = False )
185213 """Contains a dictionary of field names and values that should be added."""
186214
187215 @property
188- def _file_path (self ):
216+ def _file_path (self ) -> None | str :
189217 """Returns the file path"""
190218 return self .resolve_from_file .get ("path" )
191219
192220 def __attrs_post_init__ (self ):
193221 if self ._file_path :
194222 getter = GetterFactory .from_string (self ._file_path )
195223 if isinstance (getter , RefreshableGetter ):
196- getter .add_callback (self ._add_from_path )
197- self ._add_from_path ()
224+ getter .add_callback (partial ( self ._add_from_path , self . _file_path ) )
225+ self ._add_from_path (self . _file_path )
198226
199- def _add_from_path (self ):
227+ def _add_from_path (self , path : str ):
200228 self ._raise_if_pattern_is_invalid ()
201- self ._raise_if_file_does_not_exist ()
202- additions = self ._get_additions ( )
229+ self ._raise_if_file_does_not_exist (path )
230+ additions = self ._get_additions_from_path ( path )
203231 if self .ignore_case :
204232 additions = {key .upper (): value for key , value in additions .items ()}
205233 self .additions = additions
206234
207- def _get_additions (self ) -> dict :
235+ def _get_additions_from_path (self , path : str ) -> dict :
208236 try :
209- additions = GetterFactory .from_string (self ._file_path ).get_dict ()
237+ additions = GetterFactory .from_string (path ).get_collection ()
238+ additions = convert_ordered_mapping_or_keep_mapping (additions )
210239 except ValueError as error :
211240 raise InvalidConfigurationError (
212- f"Error loading additions from '{ self . _file_path } ': { error } "
241+ f"Error loading additions from '{ path } ': { error } "
213242 ) from error
214243 return additions
215244
@@ -219,39 +248,44 @@ def _raise_if_pattern_is_invalid(self):
219248 f"Mapping group is missing in mapping file pattern! (Rule ID: '{ self .id } ')"
220249 )
221250
222- def _raise_if_file_does_not_exist (self ):
223- if not (self . _file_path . startswith ("http" ) or Path (self . _file_path ).is_file ()):
251+ def _raise_if_file_does_not_exist (self , path : str ):
252+ if not (path . startswith ("http" ) or Path (path ).is_file ()):
224253 raise InvalidConfigurationError (
225- f"Additions file '{ self . _file_path } ' not found! (Rule ID: '{ self .id } ')" ,
254+ f"Additions file '{ path } ' not found! (Rule ID: '{ self .id } ')" ,
226255 )
227256
257+ @property
258+ def config (self ) -> Config :
259+ """Returns the typed GenericResolverRule.Config"""
260+ return typing .cast (GenericResolverRule .Config , self ._config )
261+
228262 @property
229263 def field_mapping (self ) -> dict :
230264 """Returns the field mapping"""
231- return self ._config .field_mapping
265+ return self .config .field_mapping
232266
233267 @property
234268 def resolve_list (self ) -> dict :
235269 """Returns the resolve list"""
236- return self ._config .resolve_list
270+ return self .config .resolve_list
237271
238272 @cached_property
239- def compiled_resolve_list (self ) -> List [ Tuple [re .Pattern , str ]]:
273+ def compiled_resolve_list (self ) -> list [ tuple [re .Pattern , FieldValue ]]:
240274 """Returns the resolve list with tuple pairs of compiled patterns and values"""
241275 return [
242276 (re .compile (pattern , re .I if self .ignore_case else 0 ), val )
243- for pattern , val in self ._config .resolve_list .items ()
277+ for pattern , val in self .config .resolve_list .items ()
244278 ]
245279
246280 @property
247281 def resolve_from_file (self ) -> dict :
248282 """Returns the resolve file"""
249- return self ._config .resolve_from_file
283+ return self .config .resolve_from_file
250284
251285 @property
252286 def ignore_case (self ) -> bool :
253287 """Returns if the matching should be case-sensitive or not"""
254- return self ._config .ignore_case
288+ return self .config .ignore_case
255289
256290 @cached_property
257291 def pattern (self ) -> re .Pattern :
@@ -261,4 +295,4 @@ def pattern(self) -> re.Pattern:
261295 @property
262296 def additions (self ) -> dict :
263297 """Returns additions from the resolve file"""
264- return self ._config .additions
298+ return self .config .additions
0 commit comments