Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
247 lines (232 loc) · 10.3 KB
/
Copy pathpyproject.toml
File metadata and controls
247 lines (232 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
[project]
name = "PyMemoryEditor"
dynamic = ["version"]
description = "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."
authors = [
{ name = "Jean Loui Bernard Silva de Jesus", email = "contact@jeanloui.dev" },
]
maintainers = [
{ name = "Jean Loui Bernard Silva de Jesus", email = "contact@jeanloui.dev" },
]
license = "MIT"
license-files = ["LICENSE*"]
readme = "README.md"
keywords = [
"cheat-engine",
"memory-scanner",
"memory-editor",
"process-memory",
"pointer-scan",
"aob-scan",
"pattern-scan",
"game-hacking",
"reverse-engineering",
"debugging",
"introspection",
"ctypes",
"mcp",
"model-context-protocol",
"ai-agent",
"llm-tools",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: Science/Research",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Topic :: Games/Entertainment",
"Topic :: Scientific/Engineering",
"Topic :: Security",
"Topic :: Software Development :: Debuggers",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Monitoring",
"Typing :: Typed",
]
requires-python = ">=3.10"
# The core library is dependency-free: process discovery and read/write/scan
# are implemented natively per platform (CreateToolhelp32Snapshot on Windows,
# /proc on Linux, libproc/Mach on macOS) via ctypes.
dependencies = []
[project.optional-dependencies]
# Vectorized scan acceleration. Pulls in NumPy, which lights up the
# NumPy fast path in PyMemoryEditor.util.scan automatically — no code change
# needed. The library works identically without it (pure-Python fallback);
# this just makes the ordered/range scans ~10-30x faster on large regions.
speed = [
"numpy>=1.24",
]
# Everything the test suite needs to run, and nothing else (no linters, no
# build tools -- those are in `dev`). The app and MCP extras are referenced
# rather than restated so their version ranges live in one place; the suite
# imports both (tests/app, tests/mcp_server). pytest-xdist is required, not
# optional: `addopts` below passes `-n auto`.
tests = [
"pytest",
"pytest-xdist",
"pytest-cov",
"pytest-qt",
"hypothesis",
"PyMemoryEditor[app,mcp]",
]
# Both ranges stop at the next major, like `mcp` below: psutil 8 is announced
# with breaking changes to process_iter(attrs=...), which the process picker
# calls, and Qt majors break API (Qt 6 renamed and removed much of Qt 5's).
# psutil 7 removed nothing the app uses (Process.name/create_time,
# pid_exists, process_iter with pid/name/username, and the three exceptions);
# the app suite passes on 7.2.2.
app = [
"PySide6>=6.5,<7",
"psutil>=5.9,<8",
]
# The Model Context Protocol server (PyMemoryEditor.mcp), which lets an AI
# assistant drive the scan/refine/read loop. Only the protocol layer needs the
# SDK: PyMemoryEditor.mcp.toolset deliberately imports nothing from `mcp`, so
# the tools stay testable and the core library stays dependency-free.
# Pinned to 2.x — the SDK renamed FastMCP to MCPServer and switched its models
# to snake_case in 2.0, so 1.x cannot run this server.
mcp = [
"mcp>=2,<3",
]
# NOTE: NumPy is intentionally NOT listed here. The default test suite must
# run on the pure-Python scan path so it stays covered. The NumPy fast path
# (the `speed` extra) is exercised by a dedicated CI job that installs
# `.[dev,speed]` — see the `build-speed` job in python-package.yml.
dev = [
# The test suite and what it imports: pytest and its plugins, Hypothesis,
# the app's GUI deps (PySide6, psutil) and the MCP SDK -- the toolset tests
# run without the SDK, but the server-wiring tests (tool registration,
# schemas, the read-only gate) need the real one rather than a mock of it.
"PyMemoryEditor[tests]",
"flake8",
"mypy",
"build",
# hatchling writes Metadata-Version 2.5, which `twine check` rejects as
# invalid before twine 7 -- so `make validate` failed on an older twine.
"twine>=7",
]
[project.scripts]
# main_cli wraps main() with the SIGINT scope guard that makes Ctrl+C kill the
# app from a terminal (#76). Library callers should use main() directly, which
# installs no signal handler.
pymemoryeditor = "PyMemoryEditor.app.application:main_cli"
# The MCP server. MCP clients launch this by name (or as
# `python -m PyMemoryEditor.mcp`) and speak the protocol over stdio.
pymemoryeditor-mcp = "PyMemoryEditor.mcp.server:main"
[project.urls]
Homepage = "https://github.com/JeanExtreme002/PyMemoryEditor"
Documentation = "https://pymemoryeditor.readthedocs.io"
Repository = "https://github.com/JeanExtreme002/PyMemoryEditor"
Issues = "https://github.com/JeanExtreme002/PyMemoryEditor/issues"
Changelog = "https://github.com/JeanExtreme002/PyMemoryEditor/releases"
Funding = "https://github.com/sponsors/JeanExtreme002"
[tool.mypy]
# The Qt app uses dynamic types and depends on the optional PySide6 GUI
# toolkit, so it isn't worth annotating strictly. Library code under
# PyMemoryEditor/ (excluding app/) is the surface that ships with
# `py.typed` and should aim for clean mypy output over time.
#
# `exclude` only stops mypy from *discovering* app/ when it walks the
# package; `PyMemoryEditor/__main__.py` imports the app, and followed imports
# are still checked. The override below is what actually keeps app/ out of
# the verdict -- without it the result depended on which PySide6 stubs and
# mypy version pip happened to resolve (0 errors with one pair, 200+ with
# another), on identical code.
exclude = ["PyMemoryEditor/app/"]
ignore_missing_imports = true
warn_unused_ignores = true
# Bodies of unannotated functions are checked too, for the shared layer and
# the backends alike, under all three --platform values (CI checks each one).
check_untyped_defs = true
[[tool.mypy.overrides]]
module = ["PyMemoryEditor.app", "PyMemoryEditor.app.*"]
follow_imports = "skip"
ignore_errors = true
# The platform backends call APIs that only exist on their own OS
# (`ctypes.windll`, `set_last_error` on Windows; `os.pidfd_open`,
# `signal.pidfd_send_signal` on Linux; `os.sysconf` names on macOS), so under
# any other `--platform` those attributes look undefined. Only that error code
# is switched off for them: everything else in the backends -- return types,
# assignments, call signatures -- is checked under all three platforms CI runs.
[[tool.mypy.overrides]]
module = [
"PyMemoryEditor.win32.*",
"PyMemoryEditor.linux.*",
"PyMemoryEditor.macos.*",
]
disable_error_code = ["attr-defined"]
[tool.hatch.version]
path = "PyMemoryEditor/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["PyMemoryEditor"]
[tool.hatch.build.targets.wheel.force-include]
"PyMemoryEditor/py.typed" = "PyMemoryEditor/py.typed"
"PyMemoryEditor/app/assets/icon.svg" = "PyMemoryEditor/app/assets/icon.svg"
# Maintainer-only tooling lives under scripts/ — keep it out of the source
# distribution (the wheel already only ships the PyMemoryEditor package).
[tool.hatch.build.targets.sdist]
exclude = [
"/.github",
"/codecov.yml",
"/.coveragerc-lib",
"/.coveragerc-mcp",
"/scripts",
"/server.json",
]
# hatchling>=1.27 is required for PEP 639 metadata: the SPDX ``license = "MIT"``
# expression and ``license-files`` glob above are only understood from that
# version on. Older hatchling fails the build with a confusing error.
[build-system]
requires = ["hatchling>=1.27"]
build-backend = "hatchling.build"
# Coverage scope: the Qt app is excluded. tests/app covers its workers,
# models and smoke paths, but most of app/ is widget wiring that the suite
# does not drive, so counting it would make the gate measure the UI rather
# than the library. The library code (everything else) is what we track
# regressions against.
[tool.coverage.run]
source = ["PyMemoryEditor"]
omit = [
"PyMemoryEditor/app/*",
"PyMemoryEditor/__main__.py",
"PyMemoryEditor/mcp/__main__.py",
]
[tool.coverage.report]
show_missing = true
skip_empty = true
# Parallelize the suite with pytest-xdist. ``--dist=loadfile`` keeps each
# ``test_*.py`` together on a single worker (module-scoped fixtures stay
# valid), and ``-n auto`` picks one worker per available CPU. Override with
# ``pytest -n 0`` for serial debugging.
#
# Fast vs slow split:
# pytest -m "not slow" # ~20 s — unit/GUI/MCP tests (the scan
# # property tests are marked slow).
# pytest # everything, including the live self-process
# # integration suite.
#
# CI splits by directory rather than by marker: the library/app jobs run
# `pytest tests --ignore=tests/mcp_server` and `build-mcp` runs
# `pytest tests/mcp_server`. The MCP suite attaches to the running test
# process and scans its whole address space, which on macOS is ~390 GiB, so
# paying that in all ten library cells timed the macOS job out. Locally the
# two halves are ~60 s and ~22 s.
# Mark a test (or set ``pytestmark = pytest.mark.slow`` at module scope) to
# add it to the slow bucket — see tests/memory/test_editor.py for the pattern.
[tool.pytest.ini_options]
addopts = "-n auto --dist=loadfile"
markers = [
"real_self_protection: MCP tests that check the real self/ancestor protection; the others see stand-in pids (tests/mcp_server/conftest.py)",
"slow: integration tests that read/scan the current process's whole address space (minutes, not seconds)",
"scan_heavy: tests that scan a *live* process's entire address space. Separated from `slow` because the cost is wildly platform-dependent: a macOS process exposes ~390 GiB across ~145 regions and a Windows one is not much better, so on a CI runner each scan is minutes rather than the ~1s it takes on a developer machine. CI runs these on Linux only (see the build-mcp job); the library's own live-scan coverage on the other two platforms comes from the build/build-macos jobs.",
]