PyMemoryEditor 3.2.0__tar.gz → 3.3.1__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 (213) hide show
  1. pymemoryeditor-3.3.1/CONTRIBUTING.md +199 -0
  2. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/Makefile +37 -12
  3. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PKG-INFO +12 -6
  4. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/__init__.py +1 -1
  5. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_auto_refresh_dialog.py +6 -7
  6. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_widgets.py +7 -7
  7. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/application.py +5 -6
  8. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_poll_worker.py +23 -17
  9. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_table.py +78 -38
  10. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/main_window.py +84 -30
  11. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/memory_viewer_dialog.py +5 -5
  12. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/open_process_dialog.py +1 -1
  13. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/pointer_chain_dialog.py +2 -1
  14. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/pointer_scan_dialog.py +85 -6
  15. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/results_view.py +76 -13
  16. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scan_worker.py +10 -0
  17. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scanner_panel.py +1 -1
  18. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/value_types.py +3 -3
  19. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/functions.py +58 -25
  20. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/libc.py +2 -3
  21. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/process.py +43 -88
  22. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/functions.py +150 -56
  23. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/process.py +60 -102
  24. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/config.py +46 -21
  25. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/policy.py +191 -24
  26. pymemoryeditor-3.3.1/PyMemoryEditor/mcp/procinfo.py +258 -0
  27. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/server.py +232 -25
  28. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/session.py +74 -22
  29. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/toolset.py +318 -100
  30. pymemoryeditor-3.3.1/PyMemoryEditor/process/_guard.py +43 -0
  31. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/abstract.py +273 -31
  32. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/info.py +3 -1
  33. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/pointer_scan.py +134 -13
  34. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/region.py +10 -2
  35. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/scanning.py +60 -35
  36. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/__init__.py +1 -0
  37. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/convert.py +13 -3
  38. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/pattern.py +11 -1
  39. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/scan.py +53 -2
  40. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_protections.py +1 -1
  41. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/functions.py +115 -40
  42. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/process.py +48 -100
  43. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/SECURITY.md +25 -0
  44. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/errors.md +18 -1
  45. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/utilities.md +13 -0
  46. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/conf.py +1 -1
  47. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/allocate-free.md +12 -0
  48. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/modules-threads.md +10 -1
  49. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pattern-scan.md +6 -0
  50. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pointer-scan.md +4 -0
  51. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pointers.md +9 -1
  52. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/read-write.md +10 -0
  53. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/searching.md +5 -0
  54. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/installation.md +2 -1
  55. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/mcp.md +70 -31
  56. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/platform-notes.md +34 -1
  57. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/pyproject.toml +38 -24
  58. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_cheat_entry.py +141 -0
  59. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_smoke.py +183 -0
  60. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_cheat_poll_worker.py +8 -3
  61. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_main_window_lifecycle.py +128 -0
  62. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_results_model.py +61 -3
  63. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/conftest.py +35 -0
  64. pymemoryeditor-3.3.1/tests/core/test_abstract_process.py +208 -0
  65. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_parsing.py +25 -0
  66. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_policy.py +211 -0
  67. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_server.py +257 -1
  68. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_session.py +95 -0
  69. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_toolset.py +144 -0
  70. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_search_argument_errors.py +56 -0
  71. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_typed_accessors.py +43 -0
  72. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_bitness_fallback_warns.py +1 -1
  73. pymemoryeditor-3.3.1/tests/platforms/test_closed_generators.py +186 -0
  74. pymemoryeditor-3.3.1/tests/platforms/test_linux_proc_decoding.py +106 -0
  75. pymemoryeditor-3.3.1/tests/platforms/test_linux_snapshot_passthrough.py +109 -0
  76. pymemoryeditor-3.3.1/tests/platforms/test_macos_close.py +75 -0
  77. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_load_commands.py +63 -0
  78. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_protect.py +77 -0
  79. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_region_walk.py +76 -0
  80. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_snapshot.py +49 -0
  81. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_backend_guards.py +249 -0
  82. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_pointer_chain.py +29 -0
  83. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_pointer_scan.py +302 -2
  84. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_process_lookup.py +12 -0
  85. pymemoryeditor-3.3.1/tests/scan/_fakes.py +65 -0
  86. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_chunking_integration.py +27 -0
  87. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_pattern_scan.py +101 -0
  88. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_region_snapshot.py +58 -0
  89. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan.py +51 -0
  90. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan_numpy.py +98 -0
  91. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scanning_helper.py +141 -0
  92. pymemoryeditor-3.2.0/CONTRIBUTING.md +0 -113
  93. pymemoryeditor-3.2.0/tests/platforms/test_linux_snapshot_passthrough.py +0 -60
  94. pymemoryeditor-3.2.0/tests/pointer/test_pointer_review_fixes.py +0 -224
  95. pymemoryeditor-3.2.0/tests/scan/test_scan_review_fixes.py +0 -443
  96. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.flake8 +0 -0
  97. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.gitignore +0 -0
  98. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.readthedocs.yaml +0 -0
  99. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/CODE_OF_CONDUCT.md +0 -0
  100. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/LICENSE +0 -0
  101. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/__main__.py +0 -0
  102. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/__init__.py +0 -0
  103. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_icon.py +0 -0
  104. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/assets/icon.svg +0 -0
  105. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_entry.py +0 -0
  106. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/log_console_dialog.py +0 -0
  107. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/memory_map_dialog.py +0 -0
  108. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/modules_dialog.py +0 -0
  109. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scan_types.py +0 -0
  110. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/threads_dialog.py +0 -0
  111. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/enums.py +0 -0
  112. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/types.py +0 -0
  113. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/__init__.py +0 -0
  114. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/libsystem.py +0 -0
  115. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/types.py +0 -0
  116. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/__init__.py +0 -0
  117. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/__main__.py +0 -0
  118. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/__init__.py +0 -0
  119. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/errors.py +0 -0
  120. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/module_info.py +0 -0
  121. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/remote_pointer.py +0 -0
  122. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/thread_info.py +0 -0
  123. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/util.py +0 -0
  124. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/py.typed +0 -0
  125. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/scan_numpy.py +0 -0
  126. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/__init__.py +0 -0
  127. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
  128. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
  129. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
  130. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
  131. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/types.py +0 -0
  132. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/README.md +0 -0
  133. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/screenshots/app.png +0 -0
  134. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/social-preview.html +0 -0
  135. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/social-preview.png +0 -0
  136. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_static/.gitkeep +0 -0
  137. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_static/custom.css +0 -0
  138. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_templates/layout.html +0 -0
  139. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/enums.md +0 -0
  140. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/index.md +0 -0
  141. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/memory-region.md +0 -0
  142. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/module-info.md +0 -0
  143. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/openprocess.md +0 -0
  144. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/pointer-path.md +0 -0
  145. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/remote-pointer.md +0 -0
  146. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/thread-info.md +0 -0
  147. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/app.md +0 -0
  148. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/contributing.md +0 -0
  149. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/funding.md +0 -0
  150. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/glossary.md +0 -0
  151. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/logging.md +0 -0
  152. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/memory-regions.md +0 -0
  153. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/opening-process.md +0 -0
  154. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/index.md +0 -0
  155. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/license.md +0 -0
  156. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/quickstart.md +0 -0
  157. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/requirements.txt +0 -0
  158. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/troubleshooting.md +0 -0
  159. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/why.md +0 -0
  160. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/__init__.py +0 -0
  161. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_pointer_parsing.py +0 -0
  162. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_scan_request.py +0 -0
  163. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_value_types.py +0 -0
  164. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_widgets.py +0 -0
  165. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_auto_refresh_dialog.py +0 -0
  166. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_open_process_dialog.py +0 -0
  167. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_refine_scan_worker.py +0 -0
  168. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/__init__.py +0 -0
  169. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/test_errors.py +0 -0
  170. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/test_logger.py +0 -0
  171. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/__init__.py +0 -0
  172. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/conftest.py +0 -0
  173. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_argument_fuzz.py +0 -0
  174. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_fake_fidelity.py +0 -0
  175. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_live.py +0 -0
  176. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_registry.py +0 -0
  177. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/__init__.py +0 -0
  178. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_allocate_free.py +0 -0
  179. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_bufflength_inference.py +0 -0
  180. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_editor.py +0 -0
  181. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_narrow_width_consistency.py +0 -0
  182. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_partial_io.py +0 -0
  183. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_read_into.py +0 -0
  184. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_str_boundary.py +0 -0
  185. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_str_decode_consistency.py +0 -0
  186. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_unaligned_scan.py +0 -0
  187. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_safety.py +0 -0
  188. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_span.py +0 -0
  189. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_str_bytes_width.py +0 -0
  190. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/__init__.py +0 -0
  191. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_bitness.py +0 -0
  192. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_error_hints.py +0 -0
  193. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_pid_reuse.py +0 -0
  194. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_types.py +0 -0
  195. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_threads.py +0 -0
  196. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_transient_reads.py +0 -0
  197. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_types.py +0 -0
  198. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_permissions.py +0 -0
  199. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_region_walk.py +0 -0
  200. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_toolhelp.py +0 -0
  201. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_wow64.py +0 -0
  202. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/__init__.py +0 -0
  203. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_remote_pointer.py +0 -0
  204. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/__init__.py +0 -0
  205. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_foreign_process.py +0 -0
  206. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_memory_region.py +0 -0
  207. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_module_enumeration.py +0 -0
  208. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_partial_name_match.py +0 -0
  209. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_process_enumeration.py +0 -0
  210. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_thread_enumeration.py +0 -0
  211. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/__init__.py +0 -0
  212. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_pattern_compile.py +0 -0
  213. {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan_properties.py +0 -0
@@ -0,0 +1,199 @@
1
+ # Contributing to PyMemoryEditor
2
+
3
+ Thanks for your interest in contributing!
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ python -m venv venv
9
+ source venv/bin/activate # On Windows: venv\Scripts\activate
10
+ make install-dev # pip install -e ".[dev]"
11
+ ```
12
+
13
+ The `dev` extra installs the full toolchain and the GUI deps.
14
+
15
+ The `Makefile` is the single source of truth for the dev commands below — run
16
+ `make help` to see every target (docs, build, coverage, etc.). The raw command
17
+ each target wraps is shown in parentheses if you'd rather run it directly.
18
+
19
+ ## Running the test suite
20
+
21
+ The tests read and write the memory of the test process itself; they should run
22
+ on any supported platform without elevated privileges.
23
+
24
+ ```bash
25
+ make test-fast # pytest tests -m "not slow" (~20 s: unit, GUI and MCP tests)
26
+ make test # pytest tests -v (everything, incl. live whole-process scans)
27
+ make test-mcp # pytest tests/mcp_server (the MCP server suite, ~10 s)
28
+ ```
29
+
30
+ `test-fast` is the loop to iterate on; the `slow` and `scan_heavy` markers
31
+ (defined in `pyproject.toml`) hold the tests that scan the test process's whole
32
+ address space, which takes minutes on CI runners. The scan property tests
33
+ (`tests/scan/test_scan_properties.py`) are marked `slow` too, so run them
34
+ directly when you touch the scan kernels:
35
+
36
+ ```bash
37
+ pytest tests/scan/test_scan_properties.py
38
+ ```
39
+
40
+ On CI the Hypothesis tests run under a derandomized `ci` profile (selected
41
+ automatically when `CI` is set, or with `HYPOTHESIS_PROFILE=ci`), so a failure
42
+ reproduces on re-run; locally they keep exploring new examples.
43
+
44
+ ### Testing one platform
45
+
46
+ The suite always exercises the backend of the OS it runs on (`win32/`,
47
+ `linux/` or `macos/`); tests written for another platform's backend skip
48
+ themselves.
49
+ So testing a platform means running the suite on it -- locally, in a VM, or
50
+ by opening a PR and reading that OS's CI cells. To mirror one CI cell on its
51
+ own OS:
52
+
53
+ ```bash
54
+ QT_QPA_PLATFORM=offscreen pytest tests --ignore=tests/mcp_server # build / build-macos
55
+ pytest tests/mcp_server -p no:pytest-qt # build-mcp (Linux)
56
+ pytest tests/mcp_server -p no:pytest-qt -m "not scan_heavy" # build-mcp (Windows, macOS)
57
+ ```
58
+
59
+ `pytest tests/platforms` runs only the platform-specific regression tests;
60
+ add `-k linux` (or `win32`, `macos`, `bitness`) to narrow them further. The
61
+ `speed` extra (NumPy) switches the scans to the vectorized path, which is what
62
+ the `build-speed` and macOS jobs run; leave it out to test the pure-Python one.
63
+
64
+ `tests/process/test_foreign_process.py` attaches to a child process. On macOS
65
+ that needs the `com.apple.security.cs.debugger` entitlement (see below), which
66
+ CI runners do not have, so it skips there: run it locally on a signed Python
67
+ before changing how the macOS backend opens a foreign task.
68
+
69
+ ## Linting
70
+
71
+ ```bash
72
+ make lint # flake8 PyMemoryEditor tests
73
+ ```
74
+
75
+ ## Type checking
76
+
77
+ ```bash
78
+ make type-check # mypy PyMemoryEditor
79
+ ```
80
+
81
+ ## Before you push
82
+
83
+ Run lint, type-check and the test suite in one go:
84
+
85
+ ```bash
86
+ make pre-commit # lint + type-check + test
87
+ ```
88
+
89
+ The CI pipeline runs the same lint, mypy and tests, and blocks merges on failure.
90
+ The library matrix runs Python 3.10–3.15 on Ubuntu and Windows plus Python
91
+ 3.10, 3.12 and 3.14 on macOS, so all three platform backends are exercised on
92
+ every push. Dedicated jobs also run the suite with the `speed` extra (NumPy) to keep
93
+ the vectorized scan path covered, and the MCP server suite once per OS. Even so,
94
+ contributors touching a specific backend are encouraged to run `pytest tests`
95
+ locally on that platform before submitting. `make ci` runs the same lint,
96
+ type-check and coverage-gated test invocations the workflow does.
97
+
98
+ ## Project layout
99
+
100
+ ```
101
+ PyMemoryEditor/
102
+ ├── __init__.py # Public API + platform dispatch
103
+ ├── enums.py # ScanTypesEnum (cross-platform)
104
+ ├── process/ # Abstract base, errors, process info, util
105
+ ├── util/ # Cross-platform helpers: scan and type conversion
106
+ ├── win32/ # Windows implementation (kernel32, user32)
107
+ ├── linux/ # Linux implementation (process_vm_readv/writev, /proc/<pid>/maps)
108
+ ├── macos/ # macOS implementation (task_for_pid, mach_vm_*)
109
+ ├── mcp/ # Model Context Protocol server exposed as `pymemoryeditor-mcp`
110
+ └── app/ # PySide6 (Qt) demo app exposed as `pymemoryeditor` CLI
111
+ ```
112
+
113
+ The three platform packages implement `AbstractProcess` from `process/abstract.py`.
114
+ The public alias `OpenProcess` is chosen at import time in `__init__.py` based on
115
+ `sys.platform`.
116
+
117
+ ### Platform-specific test notes
118
+ - **Linux**: requires `/proc/sys/kernel/yama/ptrace_scope=0` to attach to processes
119
+ not descended from the test runner. Self-process tests work without changes.
120
+ - **macOS**: opening another process requires the Python binary to be signed with
121
+ the `com.apple.security.cs.debugger` entitlement (or SIP off + root). Self-
122
+ process tests work without changes.
123
+ - **Windows**: no special privileges needed for self-process tests.
124
+
125
+ ## Submitting changes
126
+
127
+ 1. Open an issue first for bug reports or substantial features.
128
+ 2. Branch from `main`. Keep commits focused.
129
+ 3. Run `make pre-commit` (lint + type-check + tests) locally before pushing.
130
+ 4. Open a PR describing the change and how it was tested.
131
+ 5. Give the PR a [Conventional Commits](https://www.conventionalcommits.org/) title,
132
+ e.g. `fix: reject empty search targets` or `docs(mcp): document --allow-write`.
133
+ A required check lints it: the type must be one of `feat`, `fix`, `perf`,
134
+ `refactor`, `revert`, `docs`, `ci`, `build`, `chore`, `test`, `style`, and
135
+ the subject must start lowercase and not end with a period. PRs are
136
+ squash-merged, so the title becomes the commit message on `main`.
137
+
138
+ ## Versioning
139
+
140
+ PyMemoryEditor follows [Semantic Versioning](https://semver.org/). The public
141
+ API is everything documented on Read the Docs, and its *behaviour* counts as
142
+ much as its signatures: a call that keeps its name and arguments but returns
143
+ a different value, accepts or rejects different input, or touches different
144
+ memory is a breaking change.
145
+
146
+ - **Major** (`4.0.0`): any breaking change, including a behaviour change to a
147
+ documented default -- even when the old behaviour was a bug that someone
148
+ could reasonably depend on (3.0.0's signed 3-byte reads are the model: the
149
+ release notes lead with it, show the before/after and the way back).
150
+ - **Minor** (`3.3.0`): new features, and behaviour changes that only reject
151
+ what was memory-unsafe or meaningless before. When a minor release does
152
+ change a default (an opt-out such as `aligned=False` must exist), its
153
+ release notes say **Behaviour change** in bold.
154
+ - **Patch** (`3.2.1`): fixes that make the code do what the docs already
155
+ said.
156
+
157
+ The changelog is the
158
+ [GitHub Releases](https://github.com/JeanExtreme002/PyMemoryEditor/releases)
159
+ page: describe every user-visible change in the PR so it can be carried into
160
+ the release notes.
161
+
162
+ ## Release process
163
+
164
+ Releases are cut by publishing a GitHub Release; nothing is uploaded from a
165
+ laptop.
166
+
167
+ 1. Open a `chore: bump version to X.Y.Z` PR that sets the version in five
168
+ places: `__version__` in `PyMemoryEditor/__init__.py`, the fallback
169
+ `release` in `docs/conf.py`, and in `server.json` both `version` fields
170
+ plus the `PyMemoryEditor[mcp]==X.Y.Z` pin in the package arguments.
171
+ `tests/mcp_server/test_registry.py` fails if the package and `server.json`
172
+ disagree.
173
+ 2. Merge it and wait for the `Python Package` run on `main` to go green.
174
+ 3. Publish a GitHub Release with the tag `vX.Y.Z` on that commit; its notes
175
+ are the changelog for the version.
176
+ `.github/workflows/publish.yml` then checks that the tag matches
177
+ `__version__` and that the commit has a green `Python Package` run (it
178
+ waits for one still in progress), builds and `twine check --strict`s the
179
+ distributions, uploads them to PyPI by trusted publishing (no token), and
180
+ lists the version on the MCP Registry.
181
+
182
+ If the CI gate fails because `main`'s run was cancelled by a later push, run
183
+ `gh workflow run python-package.yml --ref vX.Y.Z` and re-run the release
184
+ workflow once it is green. `make publish` is a manual fallback for when the
185
+ workflow itself is unavailable: it refuses to run outside a clean checkout of
186
+ the `v<__version__>` tag, but it uploads by API token and does not update the
187
+ MCP Registry.
188
+
189
+ ## Reporting bugs
190
+
191
+ Please include:
192
+ - Operating system and architecture (e.g. Windows 11 x64, Ubuntu 22.04 x64).
193
+ - Python version (`python --version`).
194
+ - A minimal reproducer if possible.
195
+ - For Linux: whether `/proc/sys/kernel/yama/ptrace_scope` is `0` or `1`.
196
+
197
+ ## Security
198
+
199
+ If you find a security issue, please see [`SECURITY.md`](https://github.com/JeanExtreme002/PyMemoryEditor/blob/main/SECURITY.md). **Do not** report via GitHub issues.
@@ -31,7 +31,7 @@ help:
31
31
  @echo " $(YELLOW)run-app$(NC) - Run the PyMemoryEditor App"
32
32
  @echo " $(YELLOW)install-dev$(NC) - Install development dependencies"
33
33
  @echo " $(YELLOW)test$(NC) - Run the full test suite (incl. live whole-process scans)"
34
- @echo " $(YELLOW)test-fast$(NC) - Run the fast lane only (-m \"not slow\", ~20 s)"
34
+ @echo " $(YELLOW)test-fast$(NC) - Run the fast lane only (-m \"not slow\", ~20 s; skips the scan property tests)"
35
35
  @echo " $(YELLOW)test-mcp$(NC) - Run the MCP server suite"
36
36
  @echo " $(YELLOW)test-verbose$(NC) - Run tests with verbose output"
37
37
  @echo " $(YELLOW)test-coverage$(NC) - Run tests with coverage report"
@@ -42,7 +42,8 @@ help:
42
42
  @echo " $(YELLOW)build-wheel$(NC) - Build wheel package"
43
43
  @echo " $(YELLOW)build-sdist$(NC) - Build source distribution"
44
44
  @echo " $(YELLOW)validate$(NC) - Validate package"
45
- @echo " $(YELLOW)publish$(NC) - Publish to PyPI by token (emergency path; releases go through GitHub Releases)"
45
+ @echo " $(YELLOW)publish$(NC) - MANUAL FALLBACK: upload to PyPI by token from a checkout of the release tag"
46
+ @echo " (releases go through GitHub Releases; see CONTRIBUTING.md)"
46
47
  @echo " $(YELLOW)publish-test$(NC) - Publish to Test PyPI"
47
48
  @echo " $(YELLOW)ci$(NC) - Mirror the CI workflow: lint, type-check, both coverage-gated test runs"
48
49
  @echo " $(YELLOW)version$(NC) - Show current version"
@@ -56,6 +57,7 @@ help:
56
57
  @echo " $(YELLOW)venv$(NC) - Create virtual environment"
57
58
  @echo " $(YELLOW)venv-activate$(NC) - Show command to activate venv"
58
59
  @echo " $(YELLOW)all$(NC) - Run full pipeline (install, lint, test, build)"
60
+ @echo " $(YELLOW)release$(NC) - MANUAL FALLBACK: pre-publish checks, then publish (same tag guard)"
59
61
 
60
62
  # Create virtual environment
61
63
  .PHONY: venv
@@ -113,8 +115,11 @@ test:
113
115
  $(PYTHON) -m pytest $(TEST_DIR) -v
114
116
  @echo "$(GREEN)Tests completed!$(NC)"
115
117
 
116
- # Fast lane: unit, property and GUI tests only (~20 s). The markers are
117
- # defined in pyproject.toml.
118
+ # Fast lane: unit, GUI and MCP tests (~20 s: ~13 s for the library and app,
119
+ # ~5 s for the MCP suite). Everything marked `slow` is left out, which
120
+ # includes the scan property tests in tests/scan/test_scan_properties.py --
121
+ # run `make test` or `pytest tests/scan/test_scan_properties.py` for those.
122
+ # The markers are defined in pyproject.toml.
118
123
  .PHONY: test-fast
119
124
  test-fast:
120
125
  @echo "$(GREEN)Running the fast test lane (-m \"not slow\")...$(NC)"
@@ -202,15 +207,34 @@ build-sdist: clean
202
207
  .PHONY: validate
203
208
  validate: build
204
209
  @echo "$(GREEN)Validating package...$(NC)"
205
- $(PYTHON) -m twine check $(DIST_DIR)/*
210
+ $(PYTHON) -m twine check --strict $(DIST_DIR)/*
206
211
  @echo "$(GREEN)Package validation completed!$(NC)"
207
212
 
208
- # Publish to PyPI by API token. This is the emergency path only: releases are
209
- # cut by publishing a GitHub Release, which .github/workflows/publish.yml
210
- # uploads to PyPI via trusted publishing (OIDC, no token) and then lists on
211
- # the MCP Registry. Use this target only if that workflow is unavailable.
213
+ # Refuse to upload anything but a clean checkout of the tag `v<__version__>`.
214
+ # publish.yml checks the tag against the version before its upload; this is
215
+ # the same guard for the manual path, plus a clean tree so the files built are
216
+ # the files tagged. It does not replace the workflow's other gate (a green
217
+ # python-package.yml run on the commit): check that by hand.
218
+ .PHONY: check-release-tag
219
+ check-release-tag:
220
+ @version=$$($(PYTHON) -c "import $(PACKAGE_NAME); print($(PACKAGE_NAME).__version__)") && \
221
+ tag="v$$version" ; \
222
+ found=$$(git tag --points-at HEAD | xargs) ; \
223
+ if ! git tag --points-at HEAD | grep -qx "$$tag"; then \
224
+ echo "$(RED)HEAD is not tagged $$tag (found: $${found:-no tag}). Check out the release tag first.$(NC)"; exit 1; \
225
+ fi; \
226
+ if [ -n "$$(git status --porcelain --untracked-files=no)" ]; then \
227
+ echo "$(RED)The working tree has uncommitted changes; publish from a clean checkout of $$tag.$(NC)"; exit 1; \
228
+ fi; \
229
+ echo "$(GREEN)HEAD is $$tag, matching __version__ $$version.$(NC)"
230
+
231
+ # Publish to PyPI by API token. MANUAL FALLBACK only: releases are cut by
232
+ # publishing a GitHub Release, which .github/workflows/publish.yml uploads to
233
+ # PyPI via trusted publishing (OIDC, no token) and then lists on the MCP
234
+ # Registry. Use this target only if that workflow is unavailable, and note it
235
+ # does not list the release on the MCP Registry.
212
236
  .PHONY: publish
213
- publish: validate
237
+ publish: check-release-tag validate
214
238
  @echo "$(YELLOW)Are you sure you want to publish to PyPI? [y/N]$(NC)" && read ans && [ $${ans:-N} = y ]
215
239
  @echo "$(GREEN)Publishing to PyPI...$(NC)"
216
240
  $(PYTHON) -m twine upload $(DIST_DIR)/*
@@ -332,9 +356,10 @@ info:
332
356
  @echo "$(GREEN)Installed packages:$(NC)"
333
357
  @$(PIP) list | grep -E "($(PACKAGE_NAME)|pytest|flake8|mypy|twine|build)"
334
358
 
335
- # Quick release workflow
359
+ # Manual release (fallback for publish.yml; see `publish`). The tag guard runs
360
+ # first so a mismatched checkout fails before the full pre-publish pipeline.
336
361
  .PHONY: release
337
- release: pre-publish publish
362
+ release: check-release-tag pre-publish publish
338
363
  @echo "$(GREEN)Release completed!$(NC)"
339
364
 
340
365
  .PHONY: release-test
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: PyMemoryEditor
3
- Version: 3.2.0
3
+ Version: 3.3.1
4
4
  Summary: Read, write and scan process memory in a few lines of Python — Cheat Engine-style scans, pointer chains and AOB search on Windows, Linux and macOS.
5
5
  Project-URL: Homepage, https://github.com/JeanExtreme002/PyMemoryEditor
6
6
  Project-URL: Documentation, https://pymemoryeditor.readthedocs.io
@@ -37,27 +37,33 @@ Classifier: Topic :: System :: Monitoring
37
37
  Classifier: Typing :: Typed
38
38
  Requires-Python: >=3.10
39
39
  Provides-Extra: app
40
- Requires-Dist: psutil<7,>=5.9; extra == 'app'
41
- Requires-Dist: pyside6>=6.5; extra == 'app'
40
+ Requires-Dist: psutil<8,>=5.9; extra == 'app'
41
+ Requires-Dist: pyside6<7,>=6.5; extra == 'app'
42
42
  Provides-Extra: dev
43
43
  Requires-Dist: build; extra == 'dev'
44
44
  Requires-Dist: flake8; extra == 'dev'
45
45
  Requires-Dist: hypothesis; extra == 'dev'
46
46
  Requires-Dist: mcp<3,>=2; extra == 'dev'
47
47
  Requires-Dist: mypy; extra == 'dev'
48
- Requires-Dist: psutil<7,>=5.9; extra == 'dev'
49
- Requires-Dist: pyside6>=6.5; extra == 'dev'
48
+ Requires-Dist: psutil<8,>=5.9; extra == 'dev'
49
+ Requires-Dist: pyside6<7,>=6.5; extra == 'dev'
50
50
  Requires-Dist: pytest; extra == 'dev'
51
51
  Requires-Dist: pytest-cov; extra == 'dev'
52
52
  Requires-Dist: pytest-qt; extra == 'dev'
53
53
  Requires-Dist: pytest-xdist; extra == 'dev'
54
- Requires-Dist: twine; extra == 'dev'
54
+ Requires-Dist: twine>=7; extra == 'dev'
55
55
  Provides-Extra: mcp
56
56
  Requires-Dist: mcp<3,>=2; extra == 'mcp'
57
57
  Provides-Extra: speed
58
58
  Requires-Dist: numpy>=1.24; extra == 'speed'
59
59
  Provides-Extra: tests
60
+ Requires-Dist: hypothesis; extra == 'tests'
61
+ Requires-Dist: mcp<3,>=2; extra == 'tests'
62
+ Requires-Dist: psutil<8,>=5.9; extra == 'tests'
63
+ Requires-Dist: pyside6<7,>=6.5; extra == 'tests'
60
64
  Requires-Dist: pytest; extra == 'tests'
65
+ Requires-Dist: pytest-cov; extra == 'tests'
66
+ Requires-Dist: pytest-qt; extra == 'tests'
61
67
  Requires-Dist: pytest-xdist; extra == 'tests'
62
68
  Description-Content-Type: text/markdown
63
69
 
@@ -8,7 +8,7 @@ Supported platforms: Windows, Linux and macOS (32-bit and 64-bit).
8
8
  """
9
9
 
10
10
  __author__ = "Jean Loui Bernard Silva de Jesus"
11
- __version__ = "3.2.0"
11
+ __version__ = "3.3.1"
12
12
 
13
13
  import logging
14
14
  import sys
@@ -3,13 +3,12 @@
3
3
  Shared base for the read-only, auto-refreshing table dialogs (Memory Map,
4
4
  Modules, Threads).
5
5
 
6
- All three did the same dance — run a one-shot read off the UI thread, repopulate
6
+ All three do the same dance — run a one-shot read off the UI thread, repopulate
7
7
  a sortable table when it returns, poll on a timer, and tear the worker down
8
- safely on close — each with its own near-identical ``_*Worker(QThread)`` and
9
- lifecycle boilerplate. That triplicated the fiddly bits (the in-flight guard
10
- that self-throttles the poll, the detach-on-shutdown safety) where a fix to one
11
- copy wouldn't reach the others. This owns the lifecycle once; subclasses provide
12
- only what differs: how to fetch the data, and how to render it.
8
+ safely on close. This owns that lifecycle once, so the fiddly bits (the
9
+ in-flight guard that self-throttles the poll, the detach-on-shutdown safety)
10
+ live in one place; subclasses provide only what differs: how to fetch the
11
+ data, and how to render it.
13
12
 
14
13
  It also owns *when* a failure is worth interrupting the user — see
15
14
  :meth:`AutoRefreshTableDialog._handle_failed`.
@@ -182,7 +181,7 @@ class AutoRefreshTableDialog(TearsDownOnClose, QDialog):
182
181
  Process) *every* tick fails, and ``_on_data_failed`` pops a **modal**
183
182
  dialog. A modal runs a nested event loop, so the timer fires again while
184
183
  it is up and the failures stack — hundreds of focus-stealing dialogs the
185
- user could only escape by killing the app from another TTY (issue #74).
184
+ user could only escape by killing the app from another TTY.
186
185
 
187
186
  Reporting is therefore tied to the two moments where a failure changes
188
187
  what the user sees: the **first** fetch (empty window) and the one that
@@ -2,8 +2,8 @@
2
2
 
3
3
  """Small Qt widgets shared between dialogs.
4
4
 
5
- Centralises tiny helpers (numeric sort items, hex address parsing) that
6
- previously appeared duplicated across several dialog modules.
5
+ Centralises tiny helpers (numeric sort items, hex address parsing) shared by
6
+ several dialog modules.
7
7
  """
8
8
 
9
9
  import threading
@@ -46,9 +46,9 @@ def shutdown_worker_thread(worker: Optional[QThread], wait_ms: int = 2000) -> No
46
46
  cancel()
47
47
  try:
48
48
  # The four-argument static form on purpose: bare `worker.disconnect()`
49
- # raises TypeError in PySide6 (so this except swallowed it and nothing
50
- # was disconnected), and `worker.disconnect(worker)` only drops
51
- # connections whose *receiver* is the worker — ours belong to the dialog.
49
+ # raises TypeError in PySide6, and `worker.disconnect(worker)` only
50
+ # drops connections whose *receiver* is the worker — ours belong to the
51
+ # dialog.
52
52
  QObject.disconnect(worker, None, None, None)
53
53
  except (RuntimeError, TypeError):
54
54
  pass
@@ -141,8 +141,8 @@ class TearsDownOnClose:
141
141
  ``QDialog`` delivers a ``QCloseEvent`` only for a real close. The app's own
142
142
  "Close" buttons call ``accept()`` and Esc reaches ``reject()``; both land in
143
143
  ``done()``, which hides the dialog and emits ``finished`` *without* any
144
- close event — so a teardown living in ``closeEvent`` never ran, and the
145
- dialog kept polling, hidden and unreachable.
144
+ close event — so a teardown living in ``closeEvent`` would never run, and
145
+ the dialog would keep polling, hidden and unreachable.
146
146
 
147
147
  Subclasses implement :meth:`_teardown`; the mixin runs it exactly once,
148
148
  whichever way the dialog is dismissed, and must precede ``QDialog`` in the
@@ -542,8 +542,7 @@ def main(argv=None):
542
542
 
543
543
  ``argv`` defaults to ``sys.argv`` so packaging tools (which call
544
544
  ``main()`` with no arguments) keep working. Tests and embedders can pass
545
- an explicit list — previously a positional ``*args`` was accepted but
546
- ignored, which made the parameter meaningless.
545
+ an explicit list.
547
546
 
548
547
  Terminal users want Ctrl+C to kill the app, which takes a process-wide
549
548
  signal change — see :func:`main_cli`, the console-script entry point.
@@ -602,8 +601,8 @@ def main(argv=None):
602
601
  # Change Process… released that one and attached the window
603
602
  # to another.
604
603
  window.process.close()
605
- except Exception:
606
- pass
604
+ except Exception: # noqa: BLE001 — exiting anyway; keep the cause
605
+ _LOG.debug("Closing the process handle on exit failed", exc_info=True)
607
606
 
608
607
 
609
608
  def main_cli(argv=None):
@@ -611,9 +610,9 @@ def main_cli(argv=None):
611
610
  Entry point for the ``pymemoryeditor`` console script and ``python -m``.
612
611
 
613
612
  Qt's event loop blocks inside C++, so Python's default SIGINT handler only
614
- runs once the interpreter next regains control. In practice it raised
613
+ runs once the interpreter next regains control. In practice it raises
615
614
  KeyboardInterrupt inside our own _PointerCursorFilter.eventFilter override,
616
- where PySide6 swallows it and merely prints a traceback (#76). Hand SIGINT
615
+ where PySide6 swallows it and merely prints a traceback. Hand SIGINT
617
616
  back to the OS for the duration of the run so Ctrl+C from a terminal
618
617
  terminates the app immediately.
619
618
 
@@ -56,8 +56,8 @@ class _CheatPollWorker(QThread):
56
56
  with the current ``{key: "ErrorType: message"}`` map. The signal fires only
57
57
  when that set *changes* (a freeze starts or stops failing), so the UI gets a
58
58
  persistent cue without 10 Hz spam, and the first failure of each entry is
59
- logged once. Previously the exception was swallowed silently, so a freeze
60
- that never landed looked identical to one that did.
59
+ logged once. Swallowing the exception instead would make a freeze that
60
+ never landed look identical to one that did.
61
61
  """
62
62
 
63
63
  values_ready = Signal(object) # list[tuple[int, type, int, Any]]
@@ -96,9 +96,9 @@ class _CheatPollWorker(QThread):
96
96
  ) -> None:
97
97
  """Queue a one-shot manual write to be performed on the worker thread.
98
98
 
99
- The cheat table's inline value edits used to call
100
- ``write_process_memory`` directly on the UI thread, freezing the UI when
101
- the target was slow or the page faulted. Routing them here keeps the
99
+ The cheat table's inline value edits come through here: calling
100
+ ``write_process_memory`` directly on the UI thread would freeze the UI
101
+ when the target is slow or the page faults. Routing them here keeps the
102
102
  syscall off the UI thread; a write that fails comes back via the
103
103
  ``write_failed`` signal. Latency is at most one tick (~100 ms), which is
104
104
  imperceptible for a manual edit.
@@ -115,20 +115,21 @@ class _CheatPollWorker(QThread):
115
115
  with QMutexLocker(self._mutex):
116
116
  self._stop = True
117
117
 
118
- def _drain_pending_writes(self) -> List[Tuple[int, type, int, str]]:
119
- """Perform and clear every queued manual write.
118
+ def _take_pending_writes(self) -> List[Tuple[int, type, int, Any]]:
119
+ """Detach the queued writes. The caller holds ``self._mutex``."""
120
+ pending = self._pending_writes
121
+ self._pending_writes = []
122
+ return pending
123
+
124
+ def _perform_writes(
125
+ self, pending: List[Tuple[int, type, int, Any]]
126
+ ) -> List[Tuple[int, type, int, str]]:
127
+ """Perform manual writes taken with :meth:`_take_pending_writes`.
120
128
 
121
129
  Returns one ``(address, pytype, length, message)`` tuple per write that
122
- failed, so ``run`` can surface it via ``write_failed``. Reads and clears
123
- the queue under the lock, then runs the syscalls outside it so a slow
124
- write never blocks a UI thread enqueuing the next edit.
130
+ failed, so ``run`` can surface it via ``write_failed``. Runs outside the
131
+ lock, so a slow write never blocks a UI thread enqueuing the next edit.
125
132
  """
126
- with QMutexLocker(self._mutex):
127
- if not self._pending_writes:
128
- return []
129
- pending = self._pending_writes
130
- self._pending_writes = []
131
-
132
133
  failures: List[Tuple[int, type, int, str]] = []
133
134
  for address, pytype, length, value in pending:
134
135
  if self._stopping():
@@ -146,10 +147,15 @@ class _CheatPollWorker(QThread):
146
147
 
147
148
  def run(self) -> None:
148
149
  while True:
150
+ # Snapshot and queued writes are taken together, under one lock:
151
+ # an inline edit on a frozen row publishes the new frozen value and
152
+ # queues its write back to back, and a snapshot copied before that
153
+ # pair would re-freeze the old value over the write just drained.
149
154
  with QMutexLocker(self._mutex):
150
155
  if self._stop:
151
156
  return
152
157
  snapshot = list(self._snapshot)
158
+ pending = self._take_pending_writes()
153
159
 
154
160
  # Never let an exception escape the loop body: a QThread whose
155
161
  # run() raises dies silently, and the table would then stop
@@ -158,7 +164,7 @@ class _CheatPollWorker(QThread):
158
164
  try:
159
165
  # Apply queued manual writes before reading, so a just-typed
160
166
  # value lands and is read back as the new current value this tick.
161
- for failure in self._drain_pending_writes():
167
+ for failure in self._perform_writes(pending):
162
168
  self.write_failed.emit(failure)
163
169
 
164
170
  if snapshot: