debug-dojo 0.8.0__tar.gz → 0.9.0__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 (28) hide show
  1. {debug_dojo-0.8.0/src/debug_dojo.egg-info → debug_dojo-0.9.0}/PKG-INFO +1 -2
  2. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/pyproject.toml +3 -3
  3. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_compare.py +31 -7
  4. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_config.py +1 -1
  5. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_config_models.py +6 -1
  6. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_execution.py +3 -4
  7. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_gamification.py +12 -9
  8. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_installers.py +3 -3
  9. {debug_dojo-0.8.0 → debug_dojo-0.9.0/src/debug_dojo.egg-info}/PKG-INFO +1 -2
  10. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/requires.txt +0 -1
  11. debug_dojo-0.9.0/tests/test_compare.py +74 -0
  12. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_config.py +6 -1
  13. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_gamification.py +14 -27
  14. debug_dojo-0.8.0/tests/test_compare.py +0 -75
  15. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/LICENSE +0 -0
  16. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/README.md +0 -0
  17. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/setup.cfg +0 -0
  18. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/__init__.py +0 -0
  19. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/__main__.py +0 -0
  20. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/_cli.py +0 -0
  21. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/install.py +0 -0
  22. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo/py.typed +0 -0
  23. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/SOURCES.txt +0 -0
  24. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/dependency_links.txt +0 -0
  25. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/entry_points.txt +0 -0
  26. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/top_level.txt +0 -0
  27. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_gamification_config.py +0 -0
  28. {debug_dojo-0.8.0 → debug_dojo-0.9.0}/tests/test_installers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debug-dojo
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: 🏣 debug dojo, a place for zen debugging
5
5
  Author-email: bwrob <bartosz.marcin.wroblewski@gmail.com>
6
6
  License-Expression: MIT
@@ -22,7 +22,6 @@ Requires-Python: <3.15,>=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: dacite>=1.0.0
25
- Requires-Dist: pyscn>=1.5.2
26
25
  Requires-Dist: rich>=10.0.0
27
26
  Requires-Dist: tomlkit>=0.7.0
28
27
  Requires-Dist: typer>=0.3.0
@@ -15,7 +15,6 @@
15
15
  ]
16
16
  dependencies = [
17
17
  "dacite>=1.0.0",
18
- "pyscn>=1.5.2",
19
18
  "rich>=10.0.0",
20
19
  "tomlkit>=0.7.0",
21
20
  "typer>=0.3.0",
@@ -32,7 +31,7 @@
32
31
  name = "debug-dojo"
33
32
  readme = "README.md"
34
33
  requires-python = ">=3.10,<3.15"
35
- version = "0.8.0"
34
+ version = "0.9.0"
36
35
 
37
36
  [project.urls]
38
37
  Changelog = "https://bwrob.github.io/debug-dojo/changelog/"
@@ -74,7 +73,8 @@
74
73
  "mkdocstrings-python>=1.17.0",
75
74
  "pymdown-extensions>=10.16.1",
76
75
  "typst>=0.14.2",
77
- ]
76
+ "refurb>=2.3.0",
77
+ ]
78
78
 
79
79
 
80
80
  [tool.uv]
@@ -71,14 +71,24 @@ def get_object_methods(obj: object) -> list[str]:
71
71
 
72
72
 
73
73
  def _is_basic_type(obj: object) -> bool:
74
- """Check if the object is a basic Python type."""
74
+ """Check if the object is a basic Python type.
75
+
76
+ Returns:
77
+ bool: True if the object is a basic Python type, False otherwise.
78
+
79
+ """
75
80
  return (
76
81
  isinstance(obj, (str, int, float, list, dict, tuple, set, bool)) or obj is None
77
82
  )
78
83
 
79
84
 
80
85
  def _get_basic_info(obj: object) -> list[Text]:
81
- """Get information for basic types."""
86
+ """Get information for basic types.
87
+
88
+ Returns:
89
+ list[Text]: A list of Rich Text objects representing the basic info.
90
+
91
+ """
82
92
  return [
83
93
  Text("Value:", style="bold"),
84
94
  Text(f" {obj!r}", style="yellow"),
@@ -88,7 +98,12 @@ def _get_basic_info(obj: object) -> list[Text]:
88
98
 
89
99
 
90
100
  def _format_section(title: str, items: list[str], empty_message: str) -> list[Text]:
91
- """Format a section of the inspection output."""
101
+ """Format a section of the inspection output.
102
+
103
+ Returns:
104
+ list[Text]: A list of Rich Text objects for the formatted section.
105
+
106
+ """
92
107
  lines: list[Text] = []
93
108
  if items:
94
109
  lines.append(Text(title, style="bold"))
@@ -100,7 +115,12 @@ def _format_section(title: str, items: list[str], empty_message: str) -> list[Te
100
115
 
101
116
 
102
117
  def _get_attributes_section(obj: object) -> list[Text]:
103
- """Get the attributes section for the object info."""
118
+ """Get the attributes section for the object info.
119
+
120
+ Returns:
121
+ list[Text]: A list of Rich Text objects for the attributes section.
122
+
123
+ """
104
124
  return _format_section(
105
125
  "Attributes:",
106
126
  get_object_attributes(obj),
@@ -109,7 +129,12 @@ def _get_attributes_section(obj: object) -> list[Text]:
109
129
 
110
130
 
111
131
  def _get_methods_section(obj: object) -> list[Text]:
112
- """Get the methods section for the object info."""
132
+ """Get the methods section for the object info.
133
+
134
+ Returns:
135
+ list[Text]: A list of Rich Text objects for the methods section.
136
+
137
+ """
113
138
  return _format_section(
114
139
  "Methods:",
115
140
  [f"{method}()" for method in get_object_methods(obj)],
@@ -132,8 +157,7 @@ def get_simplified_object_info(obj: object) -> list[Text]:
132
157
  """
133
158
  info_lines: list[Text] = []
134
159
  obj_type: str = type(obj).__name__
135
- info_lines.append(Text(f"<class '{obj_type}'>", style="cyan bold"))
136
- info_lines.append(Text(""))
160
+ info_lines.extend((Text(f"<class '{obj_type}'>", style="cyan bold"), Text("")))
137
161
 
138
162
  if _is_basic_type(obj):
139
163
  info_lines.extend(_get_basic_info(obj))
@@ -114,7 +114,7 @@ def load_raw_config(config_path: Path) -> JSON:
114
114
 
115
115
  # If config is in [tool.debug_dojo] (pyproject.toml), extract it.
116
116
  try:
117
- dojo_config = cast("JSON", config_data["tool"]["debug_dojo"])
117
+ dojo_config = cast(JSON, config_data["tool"]["debug_dojo"])
118
118
  except KeyError:
119
119
  return {}
120
120
  else:
@@ -154,7 +154,12 @@ class DebugDojoConfigV1:
154
154
  """Features to install for debugging."""
155
155
 
156
156
  def update(self) -> DebugDojoConfigV2:
157
- """Update the configuration to the latest version."""
157
+ """Update the configuration to the latest version.
158
+
159
+ Returns:
160
+ DebugDojoConfigV2: The updated configuration object.
161
+
162
+ """
158
163
  v2_exceptions = ExceptionsConfig(
159
164
  rich_traceback=self.features.rich_traceback,
160
165
  )
@@ -131,6 +131,9 @@ def _safe_execute(
131
131
  target_name (str): The name of the target.
132
132
  config (DebugDojoConfig): The configuration object.
133
133
 
134
+ Raises:
135
+ Exit: If an error occurs or the user interrupts the execution.
136
+
134
137
  """
135
138
  try:
136
139
  _ = runner(target_name, run_name="__main__")
@@ -167,10 +170,6 @@ def execute_with_debug(
167
170
  verbose (bool): If True, print verbose output.
168
171
  config (DebugDojoConfig): The debug-dojo configuration.
169
172
 
170
- Raises:
171
- typer.Exit: If the target file is not found, or if an import error occurs,
172
- or if the script exits with a non-zero code.
173
-
174
173
  """
175
174
  _configure_sys_argv(target_name, target_args)
176
175
  _install_debug_tools(target_name, target_args, verbose=verbose, config=config)
@@ -94,7 +94,12 @@ class GamificationManager:
94
94
  self.stats = self._load_stats()
95
95
 
96
96
  def _load_stats(self) -> DojoStats:
97
- """Load stats from disk."""
97
+ """Load stats from disk.
98
+
99
+ Returns:
100
+ DojoStats: The loaded user statistics.
101
+
102
+ """
98
103
  if not self.stats_file.exists():
99
104
  return DojoStats()
100
105
 
@@ -104,17 +109,15 @@ class GamificationManager:
104
109
  if not isinstance(data, dict):
105
110
  return DojoStats()
106
111
 
107
- data_dict = cast("dict[str, object]", data)
112
+ data_dict = cast(dict[str, object], data)
108
113
 
109
- history_raw = cast("list[dict[str, object]]", data_dict.get("history", []))
114
+ history_raw = cast(list[dict[str, object]], data_dict.get("history", []))
110
115
  history: list[SessionInfo] = []
111
116
  for s in history_raw:
112
117
  # Support migration from duration_seconds
113
- duration = float(cast("float", s.get("duration_minutes", 0.0)))
118
+ duration = float(cast(float, s.get("duration_minutes", 0.0)))
114
119
  if "duration_seconds" in s and duration == 0:
115
- duration = (
116
- float(cast("float", s.get("duration_seconds", 0.0))) / 60.0
117
- )
120
+ duration = float(cast(float, s.get("duration_seconds", 0.0))) / 60.0
118
121
 
119
122
  history.append(
120
123
  SessionInfo(
@@ -125,8 +128,8 @@ class GamificationManager:
125
128
  )
126
129
 
127
130
  return DojoStats(
128
- sessions=int(cast("int", data_dict.get("sessions", 0))),
129
- bugs_crushed=int(cast("int", data_dict.get("bugs_crushed", 0))),
131
+ sessions=int(cast(int, data_dict.get("sessions", 0))),
132
+ bugs_crushed=int(cast(int, data_dict.get("bugs_crushed", 0))),
130
133
  history=history,
131
134
  )
132
135
 
@@ -52,7 +52,7 @@ def use_pdb(config: PdbConfig) -> None:
52
52
  import pdb
53
53
 
54
54
  os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
55
- sys.breakpointhook = cast("Any", pdb.set_trace) # pyright: ignore[reportExplicitAny]
55
+ sys.breakpointhook = cast(Any, pdb.set_trace) # pyright: ignore[reportExplicitAny]
56
56
 
57
57
 
58
58
  def use_pudb(config: PudbConfig) -> None:
@@ -72,7 +72,7 @@ def use_pudb(config: PudbConfig) -> None:
72
72
  return
73
73
 
74
74
  os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
75
- sys.breakpointhook = cast("Any", pudb.set_trace) # pyright: ignore[reportExplicitAny]
75
+ sys.breakpointhook = cast(Any, pudb.set_trace) # pyright: ignore[reportExplicitAny]
76
76
 
77
77
 
78
78
  def use_ipdb(config: IpdbConfig) -> None:
@@ -93,7 +93,7 @@ def use_ipdb(config: IpdbConfig) -> None:
93
93
 
94
94
  os.environ[BREAKPOINT_ENV_VAR] = config.set_trace_hook
95
95
  os.environ[IPDB_CONTEXT_SIZE] = str(config.context_lines)
96
- sys.breakpointhook = cast("Any", ipdb.set_trace) # pyright: ignore[reportExplicitAny]
96
+ sys.breakpointhook = cast(Any, ipdb.set_trace) # pyright: ignore[reportExplicitAny]
97
97
 
98
98
 
99
99
  def use_debugpy(config: DebugpyConfig) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debug-dojo
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: 🏣 debug dojo, a place for zen debugging
5
5
  Author-email: bwrob <bartosz.marcin.wroblewski@gmail.com>
6
6
  License-Expression: MIT
@@ -22,7 +22,6 @@ Requires-Python: <3.15,>=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: dacite>=1.0.0
25
- Requires-Dist: pyscn>=1.5.2
26
25
  Requires-Dist: rich>=10.0.0
27
26
  Requires-Dist: tomlkit>=0.7.0
28
27
  Requires-Dist: typer>=0.3.0
@@ -1,5 +1,4 @@
1
1
  dacite>=1.0.0
2
- pyscn>=1.5.2
3
2
  rich>=10.0.0
4
3
  tomlkit>=0.7.0
5
4
  typer>=0.3.0
@@ -0,0 +1,74 @@
1
+ """Test the compare utilities."""
2
+
3
+ from rich.text import Text
4
+
5
+ from debug_dojo._compare import (
6
+ get_object_attributes,
7
+ get_object_methods,
8
+ get_simplified_object_info,
9
+ )
10
+
11
+
12
+ def test_get_object_attributes() -> None:
13
+ """Test that object attributes are correctly extracted."""
14
+
15
+ class MyClass:
16
+ """A simple class for testing."""
17
+
18
+ x: int = 10
19
+ y: str = "hello"
20
+
21
+ def my_method(self) -> None:
22
+ """Do something."""
23
+
24
+ obj = MyClass()
25
+ attributes = get_object_attributes(obj)
26
+ assert "x=10" in attributes
27
+ assert "y='hello'" in attributes
28
+ assert "my_method" not in attributes
29
+
30
+
31
+ def test_get_object_methods() -> None:
32
+ """Test that object methods are correctly extracted."""
33
+
34
+ class MyClass:
35
+ """A simple class for testing."""
36
+
37
+ x: int = 10
38
+
39
+ def my_method(self) -> None:
40
+ """Do something."""
41
+
42
+ def _private_method(self) -> None:
43
+ """Do something else."""
44
+
45
+ obj = MyClass()
46
+ methods = get_object_methods(obj)
47
+ assert "my_method" in methods
48
+ assert "_private_method" not in methods
49
+
50
+
51
+ def test_get_simplified_object_info_basic_type() -> None:
52
+ """Test simplified object info for basic types."""
53
+ info = get_simplified_object_info(123)
54
+ assert any(isinstance(line, Text) and "123" in line.plain for line in info)
55
+
56
+
57
+ def test_get_simplified_object_info_custom_object() -> None:
58
+ """Test simplified object info for custom objects."""
59
+
60
+ class MyClass:
61
+ """A simple class for testing."""
62
+
63
+ x: int = 10
64
+
65
+ def my_method(self) -> None:
66
+ """Do something."""
67
+
68
+ obj = MyClass()
69
+ info = get_simplified_object_info(obj)
70
+
71
+ # Check that basic info is present
72
+ assert any(isinstance(line, Text) and "MyClass" in line.plain for line in info)
73
+ assert any(isinstance(line, Text) and "x=10" in line.plain for line in info)
74
+ assert any(isinstance(line, Text) and "my_method" in line.plain for line in info)
@@ -17,7 +17,12 @@ from debug_dojo._config_models import DebugDojoConfig, DebuggerType
17
17
 
18
18
  @pytest.fixture
19
19
  def mock_config_file(tmp_path: Path) -> Path:
20
- """Create a mock config file."""
20
+ """Create a mock config file.
21
+
22
+ Returns:
23
+ Path: The path to the created mock configuration file.
24
+
25
+ """
21
26
  config_content = """
22
27
  [debuggers]
23
28
  default = "pudb"
@@ -11,7 +11,12 @@ from debug_dojo._gamification import BELTS, GamificationManager, SessionInfo
11
11
 
12
12
  @pytest.fixture
13
13
  def temp_stats_file(tmp_path: Path) -> Path:
14
- """Fixture providing a temporary stats file path."""
14
+ """Fixture providing a temporary stats file path.
15
+
16
+ Returns:
17
+ Path: The path to a temporary statistics file.
18
+
19
+ """
15
20
  return tmp_path / "stats.json"
16
21
 
17
22
 
@@ -26,7 +31,7 @@ def test_initial_stats(temp_stats_file: Path) -> None:
26
31
  assert belt_name == "White Belt"
27
32
  assert rank == 0
28
33
  assert next_sessions == BELTS[1][1]
29
- assert next_mins == float(BELTS[1][2])
34
+ assert next_mins == pytest.approx(float(BELTS[1][2])) # pyright: ignore[reportUnknownMemberType]
30
35
 
31
36
 
32
37
  def test_increment_session(temp_stats_file: Path) -> None:
@@ -34,39 +39,28 @@ def test_increment_session(temp_stats_file: Path) -> None:
34
39
  manager = GamificationManager(stats_path=temp_stats_file)
35
40
 
36
41
  duration_seconds = 120.0
37
-
38
42
  expected_minutes = 2.0
39
-
40
43
  expected_command = "dojo run script.py"
41
-
42
44
  manager.increment_session(
43
45
  duration_seconds=duration_seconds, command=expected_command
44
46
  )
45
47
 
46
48
  assert manager.stats.sessions == 1
47
-
48
49
  assert len(manager.stats.history) == 1
49
-
50
50
  assert manager.stats.history[0].duration_minutes == expected_minutes
51
-
52
51
  assert manager.stats.history[0].command == expected_command
53
-
54
52
  assert manager.stats.total_minutes == expected_minutes
55
53
 
56
54
  # Verify file persistence
57
-
58
55
  assert temp_stats_file.exists()
59
-
60
- data = cast("dict[str, object]", json.loads(temp_stats_file.read_text()))
61
-
56
+ data: dict[str, object] = cast(
57
+ dict[str, object], json.loads(temp_stats_file.read_text(encoding="utf-8"))
58
+ )
62
59
  assert data["sessions"] == 1
63
60
 
64
- history = cast("list[dict[str, object]]", data["history"])
65
-
61
+ history = cast(list[dict[str, object]], data["history"])
66
62
  assert len(history) == 1
67
-
68
63
  assert history[0]["duration_minutes"] == expected_minutes
69
-
70
64
  assert history[0]["command"] == expected_command
71
65
 
72
66
 
@@ -92,18 +86,13 @@ def test_load_existing_stats(temp_stats_file: Path) -> None:
92
86
  ],
93
87
  }
94
88
 
95
- _ = temp_stats_file.write_text(json.dumps(data))
89
+ _ = temp_stats_file.write_text(json.dumps(data), encoding="utf-8")
96
90
 
97
91
  manager = GamificationManager(stats_path=temp_stats_file)
98
-
99
92
  assert manager.stats.sessions == expected_sessions
100
-
101
93
  assert manager.stats.bugs_crushed == expected_bugs
102
-
103
94
  assert len(manager.stats.history) == 1
104
-
105
95
  assert manager.stats.history[0].command == "dojo run test.py"
106
-
107
96
  assert manager.stats.total_minutes == expected_mins
108
97
 
109
98
 
@@ -122,10 +111,8 @@ def test_load_legacy_stats(temp_stats_file: Path) -> None:
122
111
  ],
123
112
  }
124
113
 
125
- _ = temp_stats_file.write_text(json.dumps(data))
126
-
114
+ _ = temp_stats_file.write_text(json.dumps(data), encoding="utf-8")
127
115
  manager = GamificationManager(stats_path=temp_stats_file)
128
-
129
116
  assert manager.stats.total_minutes == expected_mins
130
117
 
131
118
 
@@ -153,7 +140,7 @@ def test_belt_progression(temp_stats_file: Path) -> None:
153
140
 
154
141
  def test_corrupt_stats_file(temp_stats_file: Path) -> None:
155
142
  """Test that corrupt stats file is handled gracefully."""
156
- _ = temp_stats_file.write_text("invalid json")
143
+ _ = temp_stats_file.write_text("invalid json", encoding="utf-8")
157
144
 
158
145
  manager = GamificationManager(stats_path=temp_stats_file)
159
146
  assert manager.stats.sessions == 0
@@ -1,75 +0,0 @@
1
- """Test the `_compare` module."""
2
-
3
- from rich.text import Text
4
-
5
- from debug_dojo._compare import (
6
- get_object_attributes,
7
- get_object_methods,
8
- get_simplified_object_info,
9
- )
10
-
11
-
12
- class TestCompare:
13
- """Test the compare utilities."""
14
-
15
- def test_get_object_attributes(self) -> None:
16
- """Test that object attributes are correctly extracted."""
17
-
18
- class MyClass:
19
- """A simple class for testing."""
20
-
21
- x: int = 10
22
- y: str = "hello"
23
-
24
- def my_method(self) -> None:
25
- """Do something."""
26
-
27
- obj = MyClass()
28
- attributes = get_object_attributes(obj)
29
- assert "x=10" in attributes
30
- assert "y='hello'" in attributes
31
- assert "my_method" not in attributes
32
-
33
- def test_get_object_methods(self) -> None:
34
- """Test that object methods are correctly extracted."""
35
-
36
- class MyClass:
37
- """A simple class for testing."""
38
-
39
- x: int = 10
40
-
41
- def my_method(self) -> None:
42
- """Do something."""
43
-
44
- def _private_method(self) -> None:
45
- """Do something else."""
46
-
47
- obj = MyClass()
48
- methods = get_object_methods(obj)
49
- assert "my_method" in methods
50
- assert "_private_method" not in methods
51
-
52
- def test_get_simplified_object_info_basic_type(self) -> None:
53
- """Test simplified object info for basic types."""
54
- info = get_simplified_object_info(123)
55
- assert any(isinstance(line, Text) and "123" in line.plain for line in info)
56
-
57
- def test_get_simplified_object_info_custom_object(self) -> None:
58
- """Test simplified object info for custom objects."""
59
-
60
- class MyClass:
61
- """A simple class for testing."""
62
-
63
- x: int = 10
64
-
65
- def my_method(self) -> None:
66
- """Do something."""
67
-
68
- obj = MyClass()
69
- info = get_simplified_object_info(obj)
70
- plain_info = "\n".join(line.plain for line in info)
71
- assert "<class 'MyClass'>" in plain_info
72
- assert "Attributes:" in plain_info
73
- assert "x=10" in plain_info
74
- assert "Methods:" in plain_info
75
- assert "my_method()" in plain_info
File without changes
File without changes
File without changes