memory-graph 0.3.9__tar.gz → 0.3.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {memory_graph-0.3.9/memory_graph.egg-info → memory_graph-0.3.10}/PKG-INFO +36 -13
  2. {memory_graph-0.3.9 → memory_graph-0.3.10}/README.md +35 -12
  3. memory_graph-0.3.10/images/add_one.png +0 -0
  4. memory_graph-0.3.10/images/bin_tree.png +0 -0
  5. memory_graph-0.3.10/images/copies.png +0 -0
  6. memory_graph-0.3.10/images/debugging.gif +0 -0
  7. memory_graph-0.3.10/images/highlight.png +0 -0
  8. memory_graph-0.3.10/images/ipython.png +0 -0
  9. memory_graph-0.3.10/images/power_set.gif +0 -0
  10. memory_graph-0.3.10/images/pyodide.png +0 -0
  11. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/__init__.py +70 -44
  12. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config.py +1 -0
  13. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config_default.py +4 -1
  14. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/memory_to_nodes.py +1 -2
  15. {memory_graph-0.3.9 → memory_graph-0.3.10/memory_graph.egg-info}/PKG-INFO +36 -13
  16. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/SOURCES.txt +4 -0
  17. {memory_graph-0.3.9 → memory_graph-0.3.10}/setup.py +1 -1
  18. memory_graph-0.3.10/src/auto_memory_graph.py +18 -0
  19. memory_graph-0.3.10/src/pyodide.html +179 -0
  20. memory_graph-0.3.9/images/add_one.png +0 -0
  21. memory_graph-0.3.9/images/bin_tree.png +0 -0
  22. memory_graph-0.3.9/images/copies.png +0 -0
  23. memory_graph-0.3.9/images/debugging.gif +0 -0
  24. memory_graph-0.3.9/images/highlight.png +0 -0
  25. memory_graph-0.3.9/images/power_set.gif +0 -0
  26. {memory_graph-0.3.9 → memory_graph-0.3.10}/LICENSE.txt +0 -0
  27. {memory_graph-0.3.9 → memory_graph-0.3.10}/MANIFEST.in +0 -0
  28. {memory_graph-0.3.9 → memory_graph-0.3.10}/TODO.txt +0 -0
  29. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/add_one.py +0 -0
  30. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree.py +0 -0
  31. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_base.png +0 -0
  32. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_dir.png +0 -0
  33. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_fail.png +0 -0
  34. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_key_value.png +0 -0
  35. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_linear.png +0 -0
  36. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/avltree_table.png +0 -0
  37. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/bin_tree.py +0 -0
  38. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copies.py +0 -0
  39. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copy_method.png +0 -0
  40. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/copy_method.py +0 -0
  41. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/create_gif.sh +0 -0
  42. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/create_images.sh +0 -0
  43. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/debugging.py +0 -0
  44. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_numpy.png +0 -0
  45. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_numpy.py +0 -0
  46. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_pandas.png +0 -0
  47. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/extension_pandas.py +0 -0
  48. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/factorial.gif +0 -0
  49. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/factorial.py +0 -0
  50. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/hash_set.png +0 -0
  51. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/hash_set.py +0 -0
  52. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/highlight.py +0 -0
  53. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable.py +0 -0
  54. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable1.png +0 -0
  55. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/immutable2.png +0 -0
  56. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/jupyter_example.ipynb +0 -0
  57. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/jupyter_example.png +0 -0
  58. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/linked_list.png +0 -0
  59. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/linked_list.py +0 -0
  60. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/many_types.png +0 -0
  61. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/many_types.py +0 -0
  62. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable.py +0 -0
  63. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable1.png +0 -0
  64. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/mutable2.png +0 -0
  65. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/power_set.py +0 -0
  66. {memory_graph-0.3.9 → memory_graph-0.3.10}/images/uva.png +0 -0
  67. {memory_graph-0.3.9 → memory_graph-0.3.10}/install.txt +0 -0
  68. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/config_helpers.py +0 -0
  69. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/extension_numpy.py +0 -0
  70. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/extension_pandas.py +0 -0
  71. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/html_table.py +0 -0
  72. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/list_view.py +0 -0
  73. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_base.py +0 -0
  74. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_key_value.py +0 -0
  75. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_linear.py +0 -0
  76. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/node_table.py +0 -0
  77. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/sequence.py +0 -0
  78. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slicer.py +0 -0
  79. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices.py +0 -0
  80. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices_iterator.py +0 -0
  81. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/slices_table_iterator.py +0 -0
  82. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test.py +0 -0
  83. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_memory_graph.py +0 -0
  84. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_memory_to_nodes.py +0 -0
  85. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_sequence.py +0 -0
  86. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slicer.py +0 -0
  87. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slices.py +0 -0
  88. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/test_slices_iterator.py +0 -0
  89. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph/utils.py +0 -0
  90. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/dependency_links.txt +0 -0
  91. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/requires.txt +0 -0
  92. {memory_graph-0.3.9 → memory_graph-0.3.10}/memory_graph.egg-info/top_level.txt +0 -0
  93. {memory_graph-0.3.9 → memory_graph-0.3.10}/setup.cfg +0 -0
  94. {memory_graph-0.3.9 → memory_graph-0.3.10}/uml/memory_graph.uxf +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: memory_graph
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Summary: Draws a graph of your data to analyze its structure.
5
5
  Home-page: https://github.com/bterwijn/memory_graph
6
6
  Author: Bas Terwijn
@@ -91,7 +91,7 @@ mg.render(data, "my_graph.pdf")
91
91
  mg.render(data, "my_graph.svg")
92
92
  mg.render(data, "my_graph.png")
93
93
  mg.render(data, "my_graph.gv") # Graphviz DOT file
94
- mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.pdf')
94
+ mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
95
95
  ```
96
96
 
97
97
  # Chapters #
@@ -112,6 +112,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
112
112
 
113
113
  [Jupyter Notebook](#jupyter-notebook)
114
114
 
115
+ [ipython](#ipython)
116
+
117
+ [In the Browser](#in-the-browser)
118
+
115
119
  [Troubleshooting](#troubleshooting)
116
120
 
117
121
 
@@ -152,7 +156,7 @@ mg.render(locals(), 'immutable2.png')
152
156
 
153
157
 
154
158
  ### Mutable Type ###
155
- With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b` is independent from `a`.
159
+ With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
156
160
 
157
161
  ```python
158
162
  import memory_graph as mg
@@ -167,7 +171,7 @@ mg.render(locals(), 'mutable2.png')
167
171
  |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
168
172
  | mutable1.png | mutable2.png |
169
173
 
170
- One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type could be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much or are smaller, which makes copying less of a concern.
174
+ One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
171
175
 
172
176
  ### Copying ###
173
177
  Python offers three different "copy" options that we will demonstrate using a nested list:
@@ -220,7 +224,7 @@ mg.show(locals())
220
224
 
221
225
 
222
226
  ## Call Stack ##
223
- The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters, `a`, `b`, and `c`.
227
+ The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
224
228
 
225
229
  ```python
226
230
  import memory_graph as mg
@@ -254,9 +258,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
254
258
  mg.block(fun, arg1, arg2, ...)
255
259
  ```
256
260
 
257
- This function first executes `fun(arg1, arg2, ...)`, then prints the current source location in the program, and blocks execution until the <Enter> key is pressed.
258
- Set `mg.block_shows_location = False` to skip printing the source location.
259
- Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
261
+ This function:
262
+ * first executes `fun(arg1, arg2, ...)`
263
+ * then prints the current source location in the program
264
+ * then blocks execution until the &lt;Enter&gt; key is pressed
265
+ * finally returns the value of the `fun()` call
266
+
267
+ to change it's behavior:
268
+ * Set `mg.block_prints_location = False` to skip printing the source location.
269
+ * Set `mg.press_enter_message = None` to skip printing "Press &lt;Enter&gt; to continue...".
260
270
 
261
271
  ### Recursion ###
262
272
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
@@ -620,9 +630,9 @@ mg.show(locals())
620
630
  ```
621
631
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_dir.png)
622
632
 
623
- Next figure out what are the attributes you want to graph and choose a Node type, there are four options.
633
+ Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
624
634
 
625
- ### 1 Node_Base ###
635
+ ### 1) Node_Base ###
626
636
  Node_base is a leaf node (with no children) and shows just a single value.
627
637
  ```python
628
638
  import memory_graph as mg
@@ -642,7 +652,7 @@ mg.show(locals())
642
652
  ```
643
653
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_base.png)
644
654
 
645
- ### 2 Node_Linear ###
655
+ ### 2) Node_Linear ###
646
656
  Node_Linear shows all the values in a line like a list.
647
657
  ```python
648
658
  import memory_graph as mg
@@ -666,7 +676,7 @@ mg.show(locals())
666
676
  ```
667
677
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_linear.png)
668
678
 
669
- ### 3 Node_Key_Value ###
679
+ ### 3) Node_Key_Value ###
670
680
  Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
671
681
  ```python
672
682
  import memory_graph as mg
@@ -690,7 +700,7 @@ mg.show(locals())
690
700
  ```
691
701
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_key_value.png)
692
702
 
693
- ### 4 Node_Table ###
703
+ ### 4) Node_Table ###
694
704
  Node_Table shows all the values as a table.
695
705
  ```python
696
706
  import memory_graph as mg
@@ -727,6 +737,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
727
737
  See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
728
738
  ![jupyter_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.png)
729
739
 
740
+ ## ipython ##
741
+ In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
742
+
743
+ Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
744
+ * Linux/Mac: ~/.ipython/profile_default/startup/
745
+ * Windows: %USERPROFILE%\.ipython\profile_default\startup\
746
+
747
+ Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
748
+ ![ipyton.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/ipython.png)
749
+
750
+ ## In the Browser ##
751
+ We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
752
+ ![pyodide.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/pyodide.png)
730
753
 
731
754
  ## Troubleshooting ##
732
755
 
@@ -72,7 +72,7 @@ mg.render(data, "my_graph.pdf")
72
72
  mg.render(data, "my_graph.svg")
73
73
  mg.render(data, "my_graph.png")
74
74
  mg.render(data, "my_graph.gv") # Graphviz DOT file
75
- mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.pdf')
75
+ mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
76
76
  ```
77
77
 
78
78
  # Chapters #
@@ -93,6 +93,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
93
93
 
94
94
  [Jupyter Notebook](#jupyter-notebook)
95
95
 
96
+ [ipython](#ipython)
97
+
98
+ [In the Browser](#in-the-browser)
99
+
96
100
  [Troubleshooting](#troubleshooting)
97
101
 
98
102
 
@@ -133,7 +137,7 @@ mg.render(locals(), 'immutable2.png')
133
137
 
134
138
 
135
139
  ### Mutable Type ###
136
- With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b` is independent from `a`.
140
+ With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
137
141
 
138
142
  ```python
139
143
  import memory_graph as mg
@@ -148,7 +152,7 @@ mg.render(locals(), 'mutable2.png')
148
152
  |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
149
153
  | mutable1.png | mutable2.png |
150
154
 
151
- One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type could be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much or are smaller, which makes copying less of a concern.
155
+ One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
152
156
 
153
157
  ### Copying ###
154
158
  Python offers three different "copy" options that we will demonstrate using a nested list:
@@ -201,7 +205,7 @@ mg.show(locals())
201
205
 
202
206
 
203
207
  ## Call Stack ##
204
- The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters, `a`, `b`, and `c`.
208
+ The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
205
209
 
206
210
  ```python
207
211
  import memory_graph as mg
@@ -235,9 +239,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
235
239
  mg.block(fun, arg1, arg2, ...)
236
240
  ```
237
241
 
238
- This function first executes `fun(arg1, arg2, ...)`, then prints the current source location in the program, and blocks execution until the &lt;Enter&gt; key is pressed.
239
- Set `mg.block_shows_location = False` to skip printing the source location.
240
- Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
242
+ This function:
243
+ * first executes `fun(arg1, arg2, ...)`
244
+ * then prints the current source location in the program
245
+ * then blocks execution until the &lt;Enter&gt; key is pressed
246
+ * finally returns the value of the `fun()` call
247
+
248
+ to change it's behavior:
249
+ * Set `mg.block_prints_location = False` to skip printing the source location.
250
+ * Set `mg.press_enter_message = None` to skip printing "Press &lt;Enter&gt; to continue...".
241
251
 
242
252
  ### Recursion ###
243
253
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
@@ -601,9 +611,9 @@ mg.show(locals())
601
611
  ```
602
612
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_dir.png)
603
613
 
604
- Next figure out what are the attributes you want to graph and choose a Node type, there are four options.
614
+ Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
605
615
 
606
- ### 1 Node_Base ###
616
+ ### 1) Node_Base ###
607
617
  Node_base is a leaf node (with no children) and shows just a single value.
608
618
  ```python
609
619
  import memory_graph as mg
@@ -623,7 +633,7 @@ mg.show(locals())
623
633
  ```
624
634
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_base.png)
625
635
 
626
- ### 2 Node_Linear ###
636
+ ### 2) Node_Linear ###
627
637
  Node_Linear shows all the values in a line like a list.
628
638
  ```python
629
639
  import memory_graph as mg
@@ -647,7 +657,7 @@ mg.show(locals())
647
657
  ```
648
658
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_linear.png)
649
659
 
650
- ### 3 Node_Key_Value ###
660
+ ### 3) Node_Key_Value ###
651
661
  Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
652
662
  ```python
653
663
  import memory_graph as mg
@@ -671,7 +681,7 @@ mg.show(locals())
671
681
  ```
672
682
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_key_value.png)
673
683
 
674
- ### 4 Node_Table ###
684
+ ### 4) Node_Table ###
675
685
  Node_Table shows all the values as a table.
676
686
  ```python
677
687
  import memory_graph as mg
@@ -708,6 +718,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
708
718
  See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
709
719
  ![jupyter_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.png)
710
720
 
721
+ ## ipython ##
722
+ In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
723
+
724
+ Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
725
+ * Linux/Mac: ~/.ipython/profile_default/startup/
726
+ * Windows: %USERPROFILE%\.ipython\profile_default\startup\
727
+
728
+ Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
729
+ ![ipyton.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/ipython.png)
730
+
731
+ ## In the Browser ##
732
+ We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
733
+ ![pyodide.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/pyodide.png)
711
734
 
712
735
  ## Troubleshooting ##
713
736
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -13,15 +13,15 @@ import sys
13
13
 
14
14
  import graphviz
15
15
 
16
- __version__ = "0.3.09"
16
+ __version__ = "0.3.10"
17
17
  __author__ = 'Bas Terwijn'
18
18
  render_filename = 'memory_graph.pdf'
19
- block_shows_location = True
19
+ block_prints_location = True
20
20
  press_enter_message = "Press <Enter> to continue..."
21
21
 
22
- def get_source_location(stack_index):
22
+ def get_source_location(stack_index=0):
23
23
  """ Helper function to get the source location of the stack with 'stack_index' of the call stack. """
24
- frameInfo = inspect.stack()[stack_index] # get frameInfo of calling frame
24
+ frameInfo = inspect.stack()[1+stack_index] # get frameInfo of calling frame
25
25
  filename= frameInfo.filename
26
26
  line_nr= frameInfo.lineno
27
27
  function = frameInfo.function
@@ -32,14 +32,15 @@ def block(fun=None, *args, **kwargs):
32
32
  Calls the given function `fun` with specified arguments and keyword arguments,
33
33
  waits for the user to press Enter, and returns the result of `fun`.
34
34
  """
35
+ stack_index = 0
35
36
  if 'stack_index' in kwargs:
36
37
  stack_index = kwargs['stack_index']
37
38
  del kwargs['stack_index']
38
39
  result = None
39
40
  if callable(fun):
40
41
  result = fun(*args, **kwargs)
41
- if memory_graph.block_shows_location:
42
- print('blocked at ' + get_source_location(stack_index), end=', ')
42
+ if memory_graph.block_prints_location:
43
+ print('blocked at ' + get_source_location(1+stack_index), end=', ')
43
44
  if memory_graph.press_enter_message:
44
45
  print(memory_graph.press_enter_message)
45
46
  input()
@@ -58,7 +59,7 @@ def create_graph(data,
58
59
  graphviz_graph = memory_to_nodes.memory_to_nodes(data)
59
60
  return graphviz_graph
60
61
 
61
- def show(data ,block=False,
62
+ def show(data, block=False,
62
63
  colors = None,
63
64
  vertical_orientations = None,
64
65
  slicers = None):
@@ -82,84 +83,84 @@ def render(data, outfile=None, block=False,
82
83
 
83
84
  # ------------ aliases
84
85
 
85
- def sl(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
86
+ def sl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
86
87
  """
87
88
  Shows the graph of locals() and blocks.
88
89
  """
89
- data = get_locals_from_calling_frame(stack_index=stack_index)
90
+ data = get_locals_from_call_stack(stack_index=1+stack_index)
90
91
  memory_graph.show(data, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
91
92
 
92
- def ss(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
93
+ def ss(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
93
94
  """
94
95
  Shows the graph of mg.get_call_stack() and blocks.
95
96
  """
96
- data = get_call_stack(stack_index=stack_index)
97
+ data = get_call_stack(stack_index=1+stack_index)
97
98
  memory_graph.show(data, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
98
99
 
99
- def bsl(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
100
+ def bsl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
100
101
  """
101
102
  Shows the graph of locals() and blocks.
102
103
  """
103
- data = get_locals_from_calling_frame(stack_index=stack_index)
104
- memory_graph.block(memory_graph.show, data, stack_index=stack_index+1, block=False,
104
+ data = get_locals_from_call_stack(stack_index=1+stack_index)
105
+ memory_graph.block(memory_graph.show, data, stack_index=1+stack_index, block=False,
105
106
  colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
106
107
 
107
- def bss(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
108
+ def bss(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
108
109
  """
109
110
  Shows the graph of mg.get_call_stack() and blocks.
110
111
  """
111
- data = get_call_stack(stack_index=stack_index)
112
- memory_graph.block(memory_graph.show, data, stack_index=stack_index+1, block=False,
112
+ data = get_call_stack(stack_index=1+stack_index)
113
+ memory_graph.block(memory_graph.show, data, stack_index=1+stack_index, block=False,
113
114
  colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
114
115
 
115
- def rl(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
116
+ def rl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
116
117
  """
117
118
  Shows the graph of locals() and blocks.
118
119
  """
119
- data = get_locals_from_calling_frame(stack_index=stack_index)
120
+ data = get_locals_from_call_stack(stack_index=1+stack_index)
120
121
  memory_graph.render(data, block=False, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
121
122
 
122
- def rs(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
123
+ def rs(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
123
124
  """
124
125
  Shows the graph of mg.get_call_stack() and blocks.
125
126
  """
126
- data = get_call_stack(stack_index=stack_index)
127
+ data = get_call_stack(stack_index=1+stack_index)
127
128
  memory_graph.render(data, block=False, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
128
129
 
129
- def brl(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
130
+ def brl(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
130
131
  """
131
132
  Shows the graph of locals() and blocks.
132
133
  """
133
- data = get_locals_from_calling_frame(stack_index=stack_index)
134
- memory_graph.block(memory_graph.render, data, stack_index=stack_index+1, block=False,
134
+ data = get_locals_from_call_stack(stack_index=1+stack_index)
135
+ memory_graph.block(memory_graph.render, data, stack_index=1+stack_index, block=False,
135
136
  colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
136
137
 
137
- def brs(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
138
+ def brs(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
138
139
  """
139
140
  Shows the graph of mg.get_call_stack() and blocks.
140
141
  """
141
- data = get_call_stack(stack_index=stack_index)
142
- memory_graph.block(memory_graph.render, data, stack_index=stack_index+1, block=False,
142
+ data = get_call_stack(stack_index=1+stack_index)
143
+ memory_graph.block(memory_graph.render, data, stack_index=1+stack_index, block=False,
143
144
  colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
144
145
 
145
- def l(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
146
+ def l(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
146
147
  """
147
148
  Shows the graph of locals() and blocks.
148
149
  """
149
- bsl(stack_index=stack_index+1, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
150
+ bsl(stack_index=1+stack_index, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
150
151
 
151
- def s(stack_index=2, colors = None, vertical_orientations = None, slicers = None):
152
+ def s(stack_index=0, colors = None, vertical_orientations = None, slicers = None):
152
153
  """
153
154
  Shows the graph of mg.get_call_stack() and blocks.
154
155
  """
155
- bss(stack_index=stack_index+1, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
156
+ bss(stack_index=1+stack_index, colors=colors, vertical_orientations=vertical_orientations, slicers=slicers)
156
157
 
157
158
 
158
159
  # ------------ call stack
159
160
 
160
- def get_locals_from_calling_frame(stack_index=1):
161
+ def get_locals_from_call_stack(stack_index=0):
161
162
  """ Helper function to get locals of the stack with 'stack_inex' of the call stack. """
162
- frameInfo = inspect.stack()[stack_index] # get frameInfo of calling frame
163
+ frameInfo = inspect.stack()[1+stack_index] # get frameInfo of calling frame
163
164
  return frameInfo.frame.f_locals
164
165
 
165
166
  def stack_frames_to_dict(frames):
@@ -169,10 +170,10 @@ def stack_frames_to_dict(frames):
169
170
  return {f"{level}: {frameInfo.function}" : frameInfo.frame.f_locals
170
171
  for level, frameInfo in enumerate(frames)}
171
172
 
172
- def get_call_stack(up_to_function="<module>",stack_index=1):
173
+ def get_call_stack(up_to_function="<module>",stack_index=0):
173
174
  """ Gets the call stack up to the function 'up_to_function'. """
174
175
  frames = reversed(list(
175
- utils.take_up_to(lambda i: i.function==up_to_function, inspect.stack()[stack_index:])
176
+ utils.take_up_to(lambda i: i.function==up_to_function, inspect.stack()[1+stack_index:])
176
177
  ))
177
178
  return stack_frames_to_dict(frames)
178
179
 
@@ -197,27 +198,52 @@ def get_call_stack_pycharm(after_function="trace_dispatch",up_to_function="<modu
197
198
  return get_call_stack_after_up_to(after_function,up_to_function)
198
199
 
199
200
  def save_call_stack(filename):
200
- """ Save the call stack to 'filename' for inspection to see what functions need to be
201
+ """ Saves the call stack to 'filename' for inspection to see what functions need to be
201
202
  filtered out to create the desired graph. """
202
203
  with open(filename,'w') as file:
203
- for f in inspect.stack():
204
- file.write(f"function:{f.function} filename:{f.filename}\n")
205
-
204
+ for frame in inspect.stack():
205
+ file.write(f"function:{frame.function} filename:{frame.filename}\n")
206
+
207
+ def print_call_stack_vars(stack_index=0):
208
+ """ Prints all variables on the call stack. """
209
+ for level, frameInfo in enumerate(reversed(inspect.stack())):
210
+ print('=====',level,frameInfo.function)
211
+ print(tuple(frameInfo.frame.f_locals.keys()))
212
+
206
213
  # ------------ jupyter filtering
207
-
214
+
208
215
  jupyter_filter_keys = {'exit','quit','v','In','Out','jupyter_filter_keys'}
209
216
  def jupyter_locals_filter(jupyter_locals):
210
217
  """ Filter out the jupyter specific keys that polute the graph. """
211
218
  return {k:v for k,v in jupyter_locals.items()
212
219
  if k not in jupyter_filter_keys and k[0] != '_'}
213
220
 
214
- def locals_jupyter(stack_index=2):
221
+ def locals_jupyter(stack_index=0):
215
222
  """ Get the locals of the calling frame in a jupyter notebook, filtering out the jupyter specific keys. """
216
- return jupyter_locals_filter(get_locals_from_calling_frame(stack_index))
223
+ return jupyter_locals_filter(get_locals_from_call_stack(1+stack_index))
217
224
 
218
- def get_call_stack_jupyter(up_to_function="<module>",stack_index=2):
225
+ def get_call_stack_jupyter(up_to_function="<module>",stack_index=0):
219
226
  """ Get the call stack in a jupyter notebook, filtering out the jupyter specific keys. """
220
- call_stack = get_call_stack(up_to_function,stack_index)
227
+ call_stack = get_call_stack(up_to_function,1+stack_index)
221
228
  globals_frame = next(iter(call_stack))
222
229
  call_stack[globals_frame] = jupyter_locals_filter(call_stack[globals_frame])
223
230
  return call_stack
231
+
232
+ # ------------ ipython filtering
233
+
234
+ ipython_filter_keys = {'mg_visualization_status', 'sys', 'ipython', 'In', 'Out', 'get_ipython', 'exit', 'quit', 'open'}
235
+ def ipython_locals_filter(ipython_locals):
236
+ """ Filter out the ipython specific keys that polute the graph. """
237
+ return {k:v for k,v in ipython_locals.items()
238
+ if k not in ipython_filter_keys and k[0] != '_'}
239
+
240
+ def locals_ipython(stack_index=0):
241
+ """ Get the locals of the calling frame in a ipython, filtering out the ipython specific keys. """
242
+ return ipython_locals_filter(get_locals_from_call_stack(1+stack_index))
243
+
244
+ def get_call_stack_ipython(up_to_function="<module>",stack_index=0):
245
+ """ Get the call stack in a ipython, filtering out the ipython specific keys. """
246
+ call_stack = get_call_stack(up_to_function,1+stack_index)
247
+ globals_frame = next(iter(call_stack))
248
+ call_stack[globals_frame] = ipython_locals_filter(call_stack[globals_frame])
249
+ return call_stack
@@ -7,6 +7,7 @@
7
7
  max_tree_depth = None
8
8
  max_missing_edges = None
9
9
  max_string_length = None
10
+ graph_stability = None
10
11
 
11
12
  not_node_types = {}
12
13
  no_child_references_types = set()
@@ -15,7 +15,7 @@ import memory_graph.utils as utils
15
15
 
16
16
  import types
17
17
 
18
- """ The maximum depth of nodes in the graph. When the graph gets too big set this to a small positive number. A `★` symbol indictes where the graph is cut short. """
18
+ """ The maximum depth of nodes in the graph. When the graph gets too big set this to a small positive number. A `✂` symbol indictes where the graph is cut short. """
19
19
  config.max_tree_depth = -1
20
20
 
21
21
  config.max_missing_edges = 3
@@ -23,6 +23,9 @@ config.max_missing_edges = 3
23
23
  """ The maximum length of strings shown in the graph. Longer strings will be truncated. """
24
24
  config.max_string_length = 42
25
25
 
26
+ """ The number of references keeping child nodes in order versus other references pullen them out. """
27
+ config.graph_stability = 10
28
+
26
29
  """ Types that by default will not have references pointing to them in the graph but instead will be visualized in the node of their parent. """
27
30
  config.not_node_types = {
28
31
  type(None), bool, int, float, complex, str,
@@ -133,8 +133,7 @@ def create_depth_of_nodes(nodes, nodes_at_depth):
133
133
  def add_subgraph(graphviz_graph, nodes_to_subgraph):
134
134
  new_node_names = [node.get_name() for node in nodes_to_subgraph]
135
135
  if len(new_node_names) > 1:
136
- #graphviz_graph.body.append('{ rank="same" '+(" -> ".join(new_node_names))+' [weight=999,style=invis]; }\n')
137
- graphviz_graph.body.append('{ rank="same" '+('; '.join(new_node_names))+'; }\n')
136
+ graphviz_graph.body.append('subgraph { rank=same; '+ ' -> '.join(new_node_names) + '[weight='+str(config.graph_stability)+', style=invis]; }\n')
138
137
 
139
138
  def add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth):
140
139
  html_table = node.get_html_table(nodes, slices, id_to_slices)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: memory_graph
3
- Version: 0.3.9
3
+ Version: 0.3.10
4
4
  Summary: Draws a graph of your data to analyze its structure.
5
5
  Home-page: https://github.com/bterwijn/memory_graph
6
6
  Author: Bas Terwijn
@@ -91,7 +91,7 @@ mg.render(data, "my_graph.pdf")
91
91
  mg.render(data, "my_graph.svg")
92
92
  mg.render(data, "my_graph.png")
93
93
  mg.render(data, "my_graph.gv") # Graphviz DOT file
94
- mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.pdf')
94
+ mg.render(data) # renders to 'mg.render_filename' with default value: 'memory_graph.pdf'
95
95
  ```
96
96
 
97
97
  # Chapters #
@@ -112,6 +112,10 @@ mg.render(data) # renders to 'mg.render_filename' (default value: 'memory_graph.
112
112
 
113
113
  [Jupyter Notebook](#jupyter-notebook)
114
114
 
115
+ [ipython](#ipython)
116
+
117
+ [In the Browser](#in-the-browser)
118
+
115
119
  [Troubleshooting](#troubleshooting)
116
120
 
117
121
 
@@ -152,7 +156,7 @@ mg.render(locals(), 'immutable2.png')
152
156
 
153
157
 
154
158
  ### Mutable Type ###
155
- With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `b` is independent from `a`.
159
+ With mutable types the result is different. In the code below variable `a` and `b` both reference the same `list` value [4, 3, 2]. A `list` is a mutable type and therefore when we change variable `a` its value **can** be mutated in place and thus `a` and `b` both reference the same new value afterwards. Thus changing `a` also changes `b` and vice versa. Sometimes we want this but other times we don't and then we will have to make a copy so that `a` and `b` are independent.
156
160
 
157
161
  ```python
158
162
  import memory_graph as mg
@@ -167,7 +171,7 @@ mg.render(locals(), 'mutable2.png')
167
171
  |:-----------------------------------------------------------:|:-------------------------------------------------------------:|
168
172
  | mutable1.png | mutable2.png |
169
173
 
170
- One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type could be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much or are smaller, which makes copying less of a concern.
174
+ One practical reason why Python makes the distinction between mutable and immutable types is that a value of a mutable type can be large, making it inefficient to copy each time we change it. Immutable values generally don't need to change as much, or are small which makes copying less of a concern.
171
175
 
172
176
  ### Copying ###
173
177
  Python offers three different "copy" options that we will demonstrate using a nested list:
@@ -220,7 +224,7 @@ mg.show(locals())
220
224
 
221
225
 
222
226
  ## Call Stack ##
223
- The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters, `a`, `b`, and `c`.
227
+ The `mg.get_call_stack()` function retrieves the entire call stack, including the local variables for each function on the stack. This enables us to visualize the local variables across all active functions simultaneously. Then by examining the graph, we can determine whether any local variables from different functions on the call stack share data. For instance, consider the function `add_one()` which adds the value `1` to each of its parameters `a`, `b`, and `c`.
224
228
 
225
229
  ```python
226
230
  import memory_graph as mg
@@ -254,9 +258,15 @@ It is often helpful to temporarily block program execution to inspect the graph.
254
258
  mg.block(fun, arg1, arg2, ...)
255
259
  ```
256
260
 
257
- This function first executes `fun(arg1, arg2, ...)`, then prints the current source location in the program, and blocks execution until the &lt;Enter&gt; key is pressed.
258
- Set `mg.block_shows_location = False` to skip printing the source location.
259
- Set `mg.press_enter_message = None` to skip printing "Press <Enter> to continue...".
261
+ This function:
262
+ * first executes `fun(arg1, arg2, ...)`
263
+ * then prints the current source location in the program
264
+ * then blocks execution until the &lt;Enter&gt; key is pressed
265
+ * finally returns the value of the `fun()` call
266
+
267
+ to change it's behavior:
268
+ * Set `mg.block_prints_location = False` to skip printing the source location.
269
+ * Set `mg.press_enter_message = None` to skip printing "Press &lt;Enter&gt; to continue...".
260
270
 
261
271
  ### Recursion ###
262
272
  The call stack is also helpful to visualize how recursion works. Here we use `mg.block()` to show each step of how recursively ```factorial(3)``` is computed:
@@ -620,9 +630,9 @@ mg.show(locals())
620
630
  ```
621
631
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_dir.png)
622
632
 
623
- Next figure out what are the attributes you want to graph and choose a Node type, there are four options.
633
+ Next figure out what are the attributes you want to graph and choose a Node type, there are four options:
624
634
 
625
- ### 1 Node_Base ###
635
+ ### 1) Node_Base ###
626
636
  Node_base is a leaf node (with no children) and shows just a single value.
627
637
  ```python
628
638
  import memory_graph as mg
@@ -642,7 +652,7 @@ mg.show(locals())
642
652
  ```
643
653
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_base.png)
644
654
 
645
- ### 2 Node_Linear ###
655
+ ### 2) Node_Linear ###
646
656
  Node_Linear shows all the values in a line like a list.
647
657
  ```python
648
658
  import memory_graph as mg
@@ -666,7 +676,7 @@ mg.show(locals())
666
676
  ```
667
677
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_linear.png)
668
678
 
669
- ### 3 Node_Key_Value ###
679
+ ### 3) Node_Key_Value ###
670
680
  Node_Key_Value shows key-value pairs like a dictionary. Note the required `items()` call at the end.
671
681
  ```python
672
682
  import memory_graph as mg
@@ -690,7 +700,7 @@ mg.show(locals())
690
700
  ```
691
701
  ![extension_numpy.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/avltree_key_value.png)
692
702
 
693
- ### 4 Node_Table ###
703
+ ### 4) Node_Table ###
694
704
  Node_Table shows all the values as a table.
695
705
  ```python
696
706
  import memory_graph as mg
@@ -727,6 +737,19 @@ mg.block(display, mg.create_graph(mg.locals_jupyter()) ) # the same but blocked
727
737
  See for example [jupyter_example.ipynb](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.ipynb).
728
738
  ![jupyter_example.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/jupyter_example.png)
729
739
 
740
+ ## ipython ##
741
+ In ipython `locals()` has additional variables that cause problems in the graph, use `mg.locals_ipython()` to get the local variables with these problematic variables filtered out. Use `mg.get_call_stack_ipython()` to get the whole call stack with these variables filtered out.
742
+
743
+ Additionally install file [auto_memory_graph.py](https://raw.githubusercontent.com/bterwijn/memory_graph/main/sc/auto_memory_graph.py) in the ipython startup directory:
744
+ * Linux/Mac: ~/.ipython/profile_default/startup/
745
+ * Windows: %USERPROFILE%\.ipython\profile_default\startup\
746
+
747
+ Then after starting 'ipython' call function `mg_switch()` to turn on/off the automatic visualization of local variables after each command.
748
+ ![ipyton.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/ipython.png)
749
+
750
+ ## In the Browser ##
751
+ We can run memory_graph in the browser: <a href="https://bterwijn.github.io/memory_graph/src/pyodide.html" target="_blank">Pyodide Example</a>
752
+ ![pyodide.png](https://raw.githubusercontent.com/bterwijn/memory_graph/main/images/pyodide.png)
730
753
 
731
754
  ## Troubleshooting ##
732
755
 
@@ -36,6 +36,7 @@ images/highlight.py
36
36
  images/immutable.py
37
37
  images/immutable1.png
38
38
  images/immutable2.png
39
+ images/ipython.png
39
40
  images/jupyter_example.ipynb
40
41
  images/jupyter_example.png
41
42
  images/linked_list.png
@@ -47,6 +48,7 @@ images/mutable1.png
47
48
  images/mutable2.png
48
49
  images/power_set.gif
49
50
  images/power_set.py
51
+ images/pyodide.png
50
52
  images/uva.png
51
53
  memory_graph/__init__.py
52
54
  memory_graph/config.py
@@ -79,4 +81,6 @@ memory_graph.egg-info/SOURCES.txt
79
81
  memory_graph.egg-info/dependency_links.txt
80
82
  memory_graph.egg-info/requires.txt
81
83
  memory_graph.egg-info/top_level.txt
84
+ src/auto_memory_graph.py
85
+ src/pyodide.html
82
86
  uml/memory_graph.uxf
@@ -11,7 +11,7 @@ long_description_from_readme = (this_directory / "README.md").read_text()
11
11
 
12
12
  setup(
13
13
  name = 'memory_graph',
14
- version = '0.3.09',
14
+ version = '0.3.10',
15
15
  description = 'Draws a graph of your data to analyze its structure.',
16
16
  long_description = long_description_from_readme,
17
17
  long_description_content_type = 'text/markdown',
@@ -0,0 +1,18 @@
1
+ import memory_graph as mg
2
+
3
+ mg_visualization_status = False
4
+ print('running:', __file__)
5
+ print('Call mg_switch() to turn on/off auto memory_graph visualization.')
6
+
7
+ def mg_visualization(execution_result):
8
+ ipython_locals = get_ipython().user_ns
9
+ mg.show(mg.ipython_locals_filter(ipython_locals))
10
+
11
+ def mg_switch():
12
+ global mg_visualization_status
13
+ mg_visualization_status = not mg_visualization_status
14
+ if mg_visualization_status:
15
+ get_ipython().events.register("post_run_cell", mg_visualization)
16
+ else:
17
+ get_ipython().events.unregister("post_run_cell", mg_visualization)
18
+ return mg_visualization_status
@@ -0,0 +1,179 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pyodide Python Runner</title>
7
+ <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/viz.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2/full.render.js"></script>
10
+ <style>
11
+ body {
12
+ font-family: Arial, sans-serif;
13
+ margin: 0;
14
+ padding: 0;
15
+ height: 100%;
16
+ }
17
+ .container {
18
+ display: grid;
19
+ grid-template-columns: 1fr 1fr;
20
+ grid-template-rows: 1fr 1fr;
21
+ height: 100vh;
22
+ }
23
+ .part {
24
+ border: 1px solid black;
25
+ display: flex;
26
+ flex-direction: column;
27
+ justify-content: flex-start;
28
+ align-items: flex-start;
29
+ padding: 10px;
30
+ box-sizing: border-box;
31
+ }
32
+ textarea {
33
+ flex: 1;
34
+ width: 100%;
35
+ resize: none;
36
+ margin-bottom: 10px;
37
+ }
38
+ button {
39
+ padding: 10px 15px;
40
+ font-size: 16px;
41
+ cursor: pointer;
42
+ }
43
+ #output {
44
+ background-color: #f4f4f4;
45
+ border: 1px solid #ddd;
46
+ white-space: pre-wrap;
47
+ }
48
+ #log {
49
+ background-color: #f4f4f4;
50
+ border: 1px solid #ddd;
51
+ white-space: pre-wrap;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <div class="container">
57
+ <div class="part"><!-- ========== Top Left ========== --> <p>Log:</p>
58
+ <textarea id="log"></textarea>
59
+ </div>
60
+
61
+ <div class="part"><!-- ========== Top Right ========== --> <p>Python Code:</p>
62
+ <textarea id="python-code">
63
+ import memory_graph as mg
64
+ import js
65
+
66
+ def add_one(a, b, c):
67
+ a += [1]
68
+ b += (1,)
69
+ c += [1]
70
+ print("display the call stack")
71
+ js.display(mg.create_graph(mg.get_call_stack()))
72
+
73
+ print("initialize test data")
74
+ a = [4, 3, 2]
75
+ b = (4, 3, 2)
76
+ c = [4, 3, 2]
77
+ print(f"a:{a} b:{b} c:{c}")
78
+
79
+ print("call function add_one()")
80
+ add_one(a, b, c.copy())
81
+ print(f"a:{a} b:{b} c:{c}")
82
+ </textarea>
83
+ <button id="run-button" disabled>Run</button>
84
+ </div>
85
+
86
+ <div class="part"><!-- ========== Bottom Left ========== --> <p>Graph:</p>
87
+ <div id="graph-container"></div>
88
+ </div>
89
+
90
+
91
+ <div class="part"><!-- ========== Bottom Right ========== --> <p>Output:</p>
92
+ <textarea id="output"></textarea>
93
+ </div>
94
+ </div>
95
+
96
+
97
+ <script>
98
+ window.display = function(graph) {
99
+ const svgContainer = document.getElementById("graph-container");
100
+ try {
101
+ // Use Viz.js to render the DOT code into SVG
102
+ const viz = new Viz();
103
+ viz.renderSVGElement(graph.source)
104
+ .then(svgElement => {
105
+ svgContainer.innerHTML = "";
106
+ svgContainer.appendChild(svgElement);
107
+ })
108
+ .catch(error => {
109
+ svgContainer.innerHTML = `Error: ${error}`;
110
+ });
111
+ } catch (error) {
112
+ svgContainer.innerHTML = `Error: ${error}`;
113
+ }
114
+ };
115
+
116
+ let memory_graph_ready = false;
117
+ let pyodide;
118
+ const logDiv = document.getElementById("log");
119
+ logDiv.textContent = "Setting up\n";
120
+
121
+ async function initializePyodide() {
122
+ // Load Pyodide and micropip
123
+ logDiv.textContent += "Loading Pyodide... "
124
+ pyodide = await loadPyodide();
125
+ logDiv.textContent +="loaded\n";
126
+ logDiv.textContent += "Loading micropip... "
127
+ await pyodide.loadPackage("micropip");
128
+ logDiv.textContent += "loaded\n";
129
+ const micropip = pyodide.pyimport("micropip");
130
+
131
+ // Installing memory_graph
132
+ try {
133
+ logDiv.textContent += "Installing memory_graph... "
134
+ await micropip.install("memory_graph");
135
+ logDiv.textContent += "installed\n";
136
+ memory_graph_ready = true;
137
+
138
+ if (memory_graph_ready) {
139
+ logDiv.textContent += "Setup complete, ready to Run\n";
140
+ const button = document.getElementById("run-button");
141
+ button.disabled = false;
142
+ }
143
+ } catch (error) {
144
+ logDiv.textContent += `Failed to install memory_graph: ${error.message}\n`;
145
+ }
146
+
147
+ }
148
+
149
+ initializePyodide(); // Call the initialization function.
150
+
151
+ document.getElementById("run-button").addEventListener("click", async () => {
152
+ const code = document.getElementById("python-code").value;
153
+ const outputDiv = document.getElementById("output");
154
+ outputDiv.textContent = ""; // Clear previous output
155
+
156
+ try {
157
+ // Redirect Python stdout to capture `print()` output
158
+ pyodide.runPython(`
159
+ import sys
160
+ import io
161
+ sys.stdout = io.StringIO()
162
+ sys.stderr = sys.stdout
163
+ `);
164
+
165
+ // Execute the user-provided code
166
+ logDiv.textContent += "running code\n"
167
+ await pyodide.runPythonAsync(code);
168
+
169
+ // Retrieve stdout content
170
+ const output = pyodide.runPython("sys.stdout.getvalue()");
171
+ outputDiv.textContent = `${output}`;
172
+ } catch (error) {
173
+ outputDiv.textContent = `${error}`;
174
+ }
175
+
176
+ });
177
+ </script>
178
+ </body>
179
+ </html>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes