| 
2 | 2 | from json import JSONDecoder  | 
3 | 3 | from typing import Union, Any, List, Optional, Type, Callable, Dict  | 
4 | 4 | from pprint import pformat  | 
 | 5 | +import textwrap  | 
 | 6 | +import re  | 
5 | 7 | 
 
  | 
6 | 8 | import yaml  | 
7 | 9 | from deprecated.classic import deprecated  | 
@@ -279,19 +281,45 @@ def MissingRequiredField(self, field_name: str) -> None:  | 
279 | 281 |         raise ValueError(f"{field_name} must be supplied")  | 
280 | 282 | 
 
  | 
281 | 283 |     def __repr__(self):  | 
282 |  | -        """Only reformat 1-layer deep to preserve __repr__ of child objects"""  | 
283 |  | -        res = {}  | 
284 |  | -        for key, val in items(self):  | 
285 |  | -            if val == [] or val == {} or val is None:  | 
286 |  | -                continue  | 
287 |  | -            res[key] = val  | 
288 |  | -        return self.__class__.__name__ + '(' + pformat(res, indent=2,  | 
289 |  | -                                                       compact=True, sort_dicts=False) + ')'  | 
 | 284 | +        return _pformat(items(self), self.__class__.__name__)  | 
290 | 285 | 
 
  | 
291 | 286 |     def __str__(self):  | 
292 |  | -        """Dump everything into a dict, recursively, stringifying it all"""  | 
293 |  | -        res = remove_empty_items(self)  | 
294 |  | -        return self.__class__.__name__ + '(' + pformat(res, indent=2, compact=True) + ')'  | 
 | 287 | +        return repr(self)  | 
 | 288 | + | 
 | 289 | +def _pformat(fields:dict, cls_name:str, indent:str = '  ') -> str:  | 
 | 290 | +    """  | 
 | 291 | +    pretty format the fields of the items of a ``YAMLRoot`` object without the wonky indentation of pformat.  | 
 | 292 | +    see ``YAMLRoot.__repr__``.  | 
 | 293 | +
  | 
 | 294 | +    formatting is similar to black - items at similar levels of nesting have similar levels of indentation,  | 
 | 295 | +    rather than getting placed at essentially random levels of indentation depending on what came before them.  | 
 | 296 | +    """  | 
 | 297 | +    res = []  | 
 | 298 | +    total_len = 0  | 
 | 299 | +    for key, val in fields:  | 
 | 300 | +        if val == [] or val == {} or val is None:  | 
 | 301 | +            continue  | 
 | 302 | +        # pformat handles everything else that isn't a YAMLRoot object, but it sure does look ugly  | 
 | 303 | +        # use it to split lines and as the thing of last resort, but otherwise indent = 0, we'll do that  | 
 | 304 | +        val_str = pformat(val, indent=0, compact=True, sort_dicts=False)  | 
 | 305 | +        # now we indent everything except the first line by indenting and then using regex to remove just the first indent  | 
 | 306 | +        val_str = re.sub(rf'\A{re.escape(indent)}', '', textwrap.indent(val_str, indent))  | 
 | 307 | +        # now recombine with the key in a format that can be re-eval'd into an object if indent is just whitespace  | 
 | 308 | +        val_str = f"'{key}': " + val_str  | 
 | 309 | + | 
 | 310 | +        # count the total length of this string so we know if we need to linebreak or not later  | 
 | 311 | +        total_len += len(val_str)  | 
 | 312 | +        res.append(val_str)  | 
 | 313 | + | 
 | 314 | +    if total_len > 80:  | 
 | 315 | +        inside = ',\n'.join(res)  | 
 | 316 | +        # we indent twice - once for the inner contents of every inner object, and one to  | 
 | 317 | +        # offset from the root element. that keeps us from needing to be recursive except for the  | 
 | 318 | +        # single pformat call  | 
 | 319 | +        inside = textwrap.indent(inside, indent)  | 
 | 320 | +        return cls_name + '({\n' + inside + '\n})'  | 
 | 321 | +    else:  | 
 | 322 | +        return cls_name + '({' + ', '.join(res) + '})'  | 
295 | 323 | 
 
  | 
296 | 324 | 
 
  | 
297 | 325 | def root_representer(dumper: yaml.Dumper, data: YAMLRoot):  | 
 | 
0 commit comments