Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/nbformat/notebooknode.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

45 statements  

1"""NotebookNode - adding attribute access to dicts""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Mapping 

6from copy import deepcopy as _deepcopy 

7 

8from ._struct import Struct 

9 

10# Values `deepcopy` returns as-is, so they can be stored without recursing. 

11_ATOMIC_TYPES = (str, int, float, bool, bytes, type(None), complex) 

12 

13 

14class NotebookNode(Struct): 

15 """A dict-like node with attribute-access""" 

16 

17 def __setitem__(self, key, value): 

18 """Set an item on the notebook.""" 

19 if isinstance(value, Mapping) and not isinstance(value, NotebookNode): 

20 value = from_dict(value) 

21 super().__setitem__(key, value) 

22 

23 def __deepcopy__(self, memo): 

24 """Deep-copy the node without going through `copy._reconstruct`. 

25 

26 `copy.deepcopy` only takes its fast path for exact `dict`s, so a `dict` 

27 subclass falls back to the generic `__reduce_ex__` machinery once per 

28 node. Notebooks get deep-copied on every `isvalid`, `normalize` and 

29 `writes` call, which makes that fallback worth avoiding. 

30 """ 

31 new = self.__class__() 

32 memo[id(self)] = new 

33 for key, value in self.items(): 

34 value_type = type(value) 

35 if value_type in _ATOMIC_TYPES: 

36 dict.__setitem__(new, key, value) 

37 elif value_type is list: 

38 # a plain dict inside a list stays a plain dict, as it would 

39 # under the generic deepcopy 

40 dict.__setitem__(new, key, [_deepcopy(item, memo) for item in value]) 

41 else: 

42 # assign through __setitem__ so a Mapping value is coerced to a 

43 # NotebookNode, which is what the generic deepcopy path did 

44 new[key] = _deepcopy(value, memo) 

45 # restore instance state (Struct's `_allownew`) last: with `_allownew` 

46 # False, __setitem__ refuses new keys. 

47 if self.__dict__: 

48 new.__dict__.update(_deepcopy(self.__dict__, memo)) 

49 return new 

50 

51 def update(self, *args, **kwargs): 

52 """ 

53 A dict-like update method based on CPython's MutableMapping `update` 

54 method. 

55 """ 

56 if len(args) > 1: 

57 raise TypeError("update expected at most 1 arguments, got %d" % len(args)) 

58 if args: 

59 other = args[0] 

60 if isinstance(other, Mapping): # noqa: SIM114 

61 for key in other: 

62 self[key] = other[key] 

63 elif hasattr(other, "keys"): 

64 for key in other: 

65 self[key] = other[key] 

66 else: 

67 for key, value in other: 

68 self[key] = value 

69 for key, value in kwargs.items(): 

70 self[key] = value 

71 

72 

73def from_dict(d): 

74 """Convert dict to dict-like NotebookNode 

75 

76 Recursively converts any dict in the container to a NotebookNode. 

77 This does not check that the contents of the dictionary make a valid 

78 notebook or part of a notebook. 

79 """ 

80 if isinstance(d, dict): 

81 return NotebookNode({k: from_dict(v) for k, v in d.items()}) 

82 if isinstance(d, (tuple, list)): 

83 return [from_dict(i) for i in d] 

84 return d