From 39313486df2bf0f5f17bcccb1b534b7ee8e59b29 Mon Sep 17 00:00:00 2001 From: leowrites Date: Mon, 30 Sep 2024 20:39:37 -0400 Subject: [PATCH 1/4] Move all function inputs up and add tests for the exclude parameter --- tests/test_debug/test_snapshot.py | 132 +++++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 20 deletions(-) diff --git a/tests/test_debug/test_snapshot.py b/tests/test_debug/test_snapshot.py index 1545c554f..a57401bde 100644 --- a/tests/test_debug/test_snapshot.py +++ b/tests/test_debug/test_snapshot.py @@ -54,6 +54,59 @@ def func_cyclic() -> list: return snapshot() +def func_with_include(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: + """ + Function for snapshot() testing with include parameter. + """ + test_var1a = "David is cool!" + test_var2a = "Students Developing Software" + return snapshot(include=include) + + +def func_with_include_nested(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: + """ + Function for snapshot() testing with include parameter. + """ + test_var1b = {"SDS_coolest_project": "PyTA"} + test_var2b = ("Leo", "tester") + return func_with_include(include=include) + + +def func_with_unserializable_objects() -> list[dict]: + """ + Function for snapshot() testing with unserializable objects. + """ + path = pathlib.PosixPath("some path") + vars_in_curr_func = [snapshot()[0]] + processed_result = snapshot_to_json(vars_in_curr_func) + json.dumps(processed_result) + return processed_result + + +def func_with_exclude( + exclude_vars: Optional[Iterable[str | re.Pattern]] = None, + include: Optional[Iterable[str | re.Pattern]] = None, +) -> list[dict]: + """ + Function for snapshot() testing with exclude parameter. + """ + test_var1a = "David is cool!" + test_var2a = "Students Developing Software" + return snapshot(exclude_vars=exclude_vars, include=include) + + +def func_with_exclude_nested( + exclude_vars: Optional[Iterable[str | re.Pattern]] = None, + include: Optional[Iterable[str | re.Pattern]] = None, +) -> list[dict]: + """ + Function for snapshot() testing with exclude parameter. + """ + test_var1b = {"SDS_coolest_project": "PyTA"} + test_var2b = ("Leo", "tester") + return func_with_exclude(exclude_vars=exclude_vars, include=include) + + def test_snapshot_one_level() -> None: """ Examines whether the snapshot() function accurately captures @@ -650,26 +703,6 @@ def test_snapshot_save_stdout(): assert result.stdout == expected_svg -def func_with_include(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: - test_var1a = "David is cool!" - test_var2a = "Students Developing Software" - return snapshot(include=include) - - -def func_with_include_nested(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: - test_var1b = {"SDS_coolest_project": "PyTA"} - test_var2b = ("Leo", "tester") - return func_with_include(include=include) - - -def func_with_unserializable_objects() -> list[dict]: - path = pathlib.PosixPath("some path") - vars_in_curr_func = [snapshot()[0]] - processed_result = snapshot_to_json(vars_in_curr_func) - json.dumps(processed_result) - return processed_result - - def test_snapshot_only_includes_function_self(): result = func_with_include(include=("func_with_include",)) assert result == [ @@ -738,3 +771,62 @@ def test_snapshot_serializes_unserializable_value(): }, {"id": 1, "type": "PosixPath", "value": repr(pathlib.PosixPath("some path"))}, ] + + +def test_snapshot_excludes_one_variable_in_current_frame(): + """ + Test snapshot() excludes one variable in the current frame. + """ + result = func_with_exclude(exclude_vars=["test_var1a"], include=["func_with_exclude"]) + assert result == [ + { + "func_with_exclude": { + "exclude_vars": ["test_var1a"], + "include": ["func_with_exclude"], + "test_var2a": "Students Developing Software", + } + } + ] + + +def test_snapshot_excludes_multiple_variables(): + """ + Test snapshot() excludes multiple variables in the current frame. + """ + result = func_with_exclude( + exclude_vars=["test_var1a", "test_var2a"], include=["func_with_exclude"] + ) + assert result == [ + { + "func_with_exclude": { + "exclude_vars": ["test_var1a", "test_var2a"], + "include": ["func_with_exclude"], + } + } + ] + + +def test_snapshot_excludes_variables_in_nested_frames(): + """ + Test snapshot() excludes variables in nested frames. + """ + result = func_with_exclude_nested( + exclude_vars=["test_var1b"], include=["func_with_exclude", "func_with_exclude_nested"] + ) + assert result == [ + { + "func_with_exclude": { + "exclude_vars": ["test_var1b"], + "include": ["func_with_exclude", "func_with_exclude_nested"], + "test_var1a": "David is cool!", + "test_var2a": "Students Developing Software", + } + }, + { + "func_with_exclude_nested": { + "exclude_vars": ["test_var1b"], + "include": ["func_with_exclude", "func_with_exclude_nested"], + "test_var2b": ("Leo", "tester"), + } + }, + ] From b65a6257fae17dd79b6829cc918be25efe1ccff8 Mon Sep 17 00:00:00 2001 From: leowrites Date: Mon, 30 Sep 2024 20:45:10 -0400 Subject: [PATCH 2/4] add regex test and implement filter --- python_ta/debug/snapshot.py | 19 ++++++++++++++++++- tests/test_debug/test_snapshot.py | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/python_ta/debug/snapshot.py b/python_ta/debug/snapshot.py index bf1fdc90d..78a35f166 100644 --- a/python_ta/debug/snapshot.py +++ b/python_ta/debug/snapshot.py @@ -37,11 +37,25 @@ def get_filtered_global_variables(frame: FrameType) -> dict: return {"__main__": true_global_vars} +def get_filtered_local_variables(frame, exclude_vars: Optional[Iterable[str | re.Pattern]]) -> dict: + """ + Helper function for filtering local variables in a frame. + """ + if exclude_vars: + return { + var: frame.f_locals[var] + for var in frame.f_locals + if not any(re.search(regex, var) for regex in exclude_vars) + } + return frame.f_locals + + def snapshot( save: bool = False, memory_viz_args: Optional[list[str]] = None, memory_viz_version: str = "latest", include: Optional[Iterable[str | re.Pattern]] = None, + exclude_vars: Optional[Iterable[str | re.Pattern]] = None, ): """Capture a snapshot of local variables from the current and outer stack frames where the 'snapshot' function is called. Returns a list of dictionaries, @@ -56,6 +70,8 @@ def snapshot( include can be used to specify a collection of function names, either as strings or regular expressions, whose variables will be captured. By default, all variables in all functions will be captured if no `include` argument is provided. + exclude_vars can be used to specify a collection of variable names, either as strings or regular expressions, + that will be excluded from the snapshot. By default, all variables will be captured if no `exclude_vars` is provided. """ variables = [] frame = inspect.currentframe().f_back @@ -63,7 +79,8 @@ def snapshot( while frame: if include is None or any(re.search(regex, frame.f_code.co_name) for regex in include): if frame.f_code.co_name != "": - variables.append({frame.f_code.co_name: frame.f_locals}) + local_vars = get_filtered_local_variables(frame, exclude_vars) + variables.append({frame.f_code.co_name: local_vars}) else: global_vars = get_filtered_global_variables(frame) variables.append(global_vars) diff --git a/tests/test_debug/test_snapshot.py b/tests/test_debug/test_snapshot.py index a57401bde..304f2f271 100644 --- a/tests/test_debug/test_snapshot.py +++ b/tests/test_debug/test_snapshot.py @@ -830,3 +830,29 @@ def test_snapshot_excludes_variables_in_nested_frames(): } }, ] + + +def test_snapshot_excludes_variables_with_regex(): + """ + Test snapshot() excludes variables in nested frames using regex. + """ + result = func_with_exclude_nested( + exclude_vars=[re.compile("test_var1.*")], + include=["func_with_exclude", "func_with_exclude_nested"], + ) + assert result == [ + { + "func_with_exclude": { + "exclude_vars": [re.compile(r"test_var1.*")], + "include": ["func_with_exclude", "func_with_exclude_nested"], + "test_var2a": "Students Developing Software", + } + }, + { + "func_with_exclude_nested": { + "exclude_vars": [re.compile(r"test_var1.*")], + "include": ["func_with_exclude", "func_with_exclude_nested"], + "test_var2b": ("Leo", "tester"), + } + }, + ] From b0b550d573e003eec012f3a78ebd68d2cc849207 Mon Sep 17 00:00:00 2001 From: leowrites Date: Mon, 30 Sep 2024 20:46:33 -0400 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 1 + tests/test_debug/test_snapshot.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef2aad51..0a8006cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### ✨ Enhancements - Added `include` filter to `snapshot` +- Added `exclude_vars` filter to `snapshot` ### 💫 New checkers diff --git a/tests/test_debug/test_snapshot.py b/tests/test_debug/test_snapshot.py index 304f2f271..724cbb1e0 100644 --- a/tests/test_debug/test_snapshot.py +++ b/tests/test_debug/test_snapshot.py @@ -843,14 +843,14 @@ def test_snapshot_excludes_variables_with_regex(): assert result == [ { "func_with_exclude": { - "exclude_vars": [re.compile(r"test_var1.*")], + "exclude_vars": [re.compile("test_var1.*")], "include": ["func_with_exclude", "func_with_exclude_nested"], "test_var2a": "Students Developing Software", } }, { "func_with_exclude_nested": { - "exclude_vars": [re.compile(r"test_var1.*")], + "exclude_vars": [re.compile("test_var1.*")], "include": ["func_with_exclude", "func_with_exclude_nested"], "test_var2b": ("Leo", "tester"), } From 0f8c013f5fcb2762b55e75ad56deac72c55cba0b Mon Sep 17 00:00:00 2001 From: leowrites Date: Tue, 1 Oct 2024 07:42:34 -0400 Subject: [PATCH 4/4] Rename `include` to `include_frames` --- CHANGELOG.md | 2 +- python_ta/debug/snapshot.py | 14 ++++--- tests/test_debug/test_snapshot.py | 61 ++++++++++++++++--------------- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8006cdb..cb4332668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### ✨ Enhancements -- Added `include` filter to `snapshot` +- Added `include_frames` filter to `snapshot` - Added `exclude_vars` filter to `snapshot` ### 💫 New checkers diff --git a/python_ta/debug/snapshot.py b/python_ta/debug/snapshot.py index 78a35f166..7ee1a79c8 100644 --- a/python_ta/debug/snapshot.py +++ b/python_ta/debug/snapshot.py @@ -37,7 +37,9 @@ def get_filtered_global_variables(frame: FrameType) -> dict: return {"__main__": true_global_vars} -def get_filtered_local_variables(frame, exclude_vars: Optional[Iterable[str | re.Pattern]]) -> dict: +def get_filtered_local_variables( + frame: FrameType, exclude_vars: Optional[Iterable[str | re.Pattern]] +) -> dict: """ Helper function for filtering local variables in a frame. """ @@ -54,7 +56,7 @@ def snapshot( save: bool = False, memory_viz_args: Optional[list[str]] = None, memory_viz_version: str = "latest", - include: Optional[Iterable[str | re.Pattern]] = None, + include_frames: Optional[Iterable[str | re.Pattern]] = None, exclude_vars: Optional[Iterable[str | re.Pattern]] = None, ): """Capture a snapshot of local variables from the current and outer stack frames @@ -67,8 +69,8 @@ def snapshot( For details on the MemoryViz CLI, see https://www.cs.toronto.edu/~david/memory-viz/docs/cli. memory_viz_version can be used to dictate version, with a default of the latest version. Note that this function is compatible only with MemoryViz version 0.3.1 and above. - include can be used to specify a collection of function names, either as strings or regular expressions, - whose variables will be captured. By default, all variables in all functions will be captured if no `include` + include_frames can be used to specify a collection of function names, either as strings or regular expressions, + whose variables will be captured. By default, all variables in all functions will be captured if no `include_frames` argument is provided. exclude_vars can be used to specify a collection of variable names, either as strings or regular expressions, that will be excluded from the snapshot. By default, all variables will be captured if no `exclude_vars` is provided. @@ -77,7 +79,9 @@ def snapshot( frame = inspect.currentframe().f_back while frame: - if include is None or any(re.search(regex, frame.f_code.co_name) for regex in include): + if include_frames is None or any( + re.search(regex, frame.f_code.co_name) for regex in include_frames + ): if frame.f_code.co_name != "": local_vars = get_filtered_local_variables(frame, exclude_vars) variables.append({frame.f_code.co_name: local_vars}) diff --git a/tests/test_debug/test_snapshot.py b/tests/test_debug/test_snapshot.py index 724cbb1e0..a695e1dac 100644 --- a/tests/test_debug/test_snapshot.py +++ b/tests/test_debug/test_snapshot.py @@ -54,22 +54,24 @@ def func_cyclic() -> list: return snapshot() -def func_with_include(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: +def func_with_include(include_frames: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: """ - Function for snapshot() testing with include parameter. + Function for snapshot() testing with include_frames parameter. """ test_var1a = "David is cool!" test_var2a = "Students Developing Software" - return snapshot(include=include) + return snapshot(include_frames=include_frames) -def func_with_include_nested(include: Optional[Iterable[str | re.Pattern]] = None) -> list[dict]: +def func_with_include_nested( + include_frames: Optional[Iterable[str | re.Pattern]] = None, +) -> list[dict]: """ - Function for snapshot() testing with include parameter. + Function for snapshot() testing with include_frames parameter. """ test_var1b = {"SDS_coolest_project": "PyTA"} test_var2b = ("Leo", "tester") - return func_with_include(include=include) + return func_with_include(include_frames=include_frames) def func_with_unserializable_objects() -> list[dict]: @@ -85,26 +87,26 @@ def func_with_unserializable_objects() -> list[dict]: def func_with_exclude( exclude_vars: Optional[Iterable[str | re.Pattern]] = None, - include: Optional[Iterable[str | re.Pattern]] = None, + include_frames: Optional[Iterable[str | re.Pattern]] = None, ) -> list[dict]: """ - Function for snapshot() testing with exclude parameter. + Function for snapshot() testing with exclude_vars parameter. """ test_var1a = "David is cool!" test_var2a = "Students Developing Software" - return snapshot(exclude_vars=exclude_vars, include=include) + return snapshot(exclude_vars=exclude_vars, include_frames=include_frames) def func_with_exclude_nested( exclude_vars: Optional[Iterable[str | re.Pattern]] = None, - include: Optional[Iterable[str | re.Pattern]] = None, + include_frames: Optional[Iterable[str | re.Pattern]] = None, ) -> list[dict]: """ - Function for snapshot() testing with exclude parameter. + Function for snapshot() testing with exclude_vars parameter. """ test_var1b = {"SDS_coolest_project": "PyTA"} test_var2b = ("Leo", "tester") - return func_with_exclude(exclude_vars=exclude_vars, include=include) + return func_with_exclude(exclude_vars=exclude_vars, include_frames=include_frames) def test_snapshot_one_level() -> None: @@ -704,11 +706,11 @@ def test_snapshot_save_stdout(): def test_snapshot_only_includes_function_self(): - result = func_with_include(include=("func_with_include",)) + result = func_with_include(include_frames=("func_with_include",)) assert result == [ { "func_with_include": { - "include": ("func_with_include",), + "include_frames": ("func_with_include",), "test_var1a": "David is cool!", "test_var2a": "Students Developing Software", } @@ -718,7 +720,7 @@ def test_snapshot_only_includes_function_self(): def test_snapshot_includes_multiple_functions(): result = func_with_include_nested( - include=( + include_frames=( "func_with_include", "func_with_include_nested", ) @@ -726,7 +728,7 @@ def test_snapshot_includes_multiple_functions(): assert result == [ { "func_with_include": { - "include": ( + "include_frames": ( "func_with_include", "func_with_include_nested", ), @@ -736,7 +738,7 @@ def test_snapshot_includes_multiple_functions(): }, { "func_with_include_nested": { - "include": ( + "include_frames": ( "func_with_include", "func_with_include_nested", ), @@ -748,11 +750,11 @@ def test_snapshot_includes_multiple_functions(): def test_snapshot_only_includes_specified_function(): - result = func_with_include_nested(include=("func_with_include_nested",)) + result = func_with_include_nested(include_frames=("func_with_include_nested",)) assert result == [ { "func_with_include_nested": { - "include": ("func_with_include_nested",), + "include_frames": ("func_with_include_nested",), "test_var1b": {"SDS_coolest_project": "PyTA"}, "test_var2b": ("Leo", "tester"), }, @@ -777,12 +779,12 @@ def test_snapshot_excludes_one_variable_in_current_frame(): """ Test snapshot() excludes one variable in the current frame. """ - result = func_with_exclude(exclude_vars=["test_var1a"], include=["func_with_exclude"]) + result = func_with_exclude(exclude_vars=["test_var1a"], include_frames=["func_with_exclude"]) assert result == [ { "func_with_exclude": { "exclude_vars": ["test_var1a"], - "include": ["func_with_exclude"], + "include_frames": ["func_with_exclude"], "test_var2a": "Students Developing Software", } } @@ -794,13 +796,13 @@ def test_snapshot_excludes_multiple_variables(): Test snapshot() excludes multiple variables in the current frame. """ result = func_with_exclude( - exclude_vars=["test_var1a", "test_var2a"], include=["func_with_exclude"] + exclude_vars=["test_var1a", "test_var2a"], include_frames=["func_with_exclude"] ) assert result == [ { "func_with_exclude": { "exclude_vars": ["test_var1a", "test_var2a"], - "include": ["func_with_exclude"], + "include_frames": ["func_with_exclude"], } } ] @@ -811,13 +813,14 @@ def test_snapshot_excludes_variables_in_nested_frames(): Test snapshot() excludes variables in nested frames. """ result = func_with_exclude_nested( - exclude_vars=["test_var1b"], include=["func_with_exclude", "func_with_exclude_nested"] + exclude_vars=["test_var1b"], + include_frames=["func_with_exclude", "func_with_exclude_nested"], ) assert result == [ { "func_with_exclude": { "exclude_vars": ["test_var1b"], - "include": ["func_with_exclude", "func_with_exclude_nested"], + "include_frames": ["func_with_exclude", "func_with_exclude_nested"], "test_var1a": "David is cool!", "test_var2a": "Students Developing Software", } @@ -825,7 +828,7 @@ def test_snapshot_excludes_variables_in_nested_frames(): { "func_with_exclude_nested": { "exclude_vars": ["test_var1b"], - "include": ["func_with_exclude", "func_with_exclude_nested"], + "include_frames": ["func_with_exclude", "func_with_exclude_nested"], "test_var2b": ("Leo", "tester"), } }, @@ -838,20 +841,20 @@ def test_snapshot_excludes_variables_with_regex(): """ result = func_with_exclude_nested( exclude_vars=[re.compile("test_var1.*")], - include=["func_with_exclude", "func_with_exclude_nested"], + include_frames=["func_with_exclude", "func_with_exclude_nested"], ) assert result == [ { "func_with_exclude": { "exclude_vars": [re.compile("test_var1.*")], - "include": ["func_with_exclude", "func_with_exclude_nested"], + "include_frames": ["func_with_exclude", "func_with_exclude_nested"], "test_var2a": "Students Developing Software", } }, { "func_with_exclude_nested": { "exclude_vars": [re.compile("test_var1.*")], - "include": ["func_with_exclude", "func_with_exclude_nested"], + "include_frames": ["func_with_exclude", "func_with_exclude_nested"], "test_var2b": ("Leo", "tester"), } },