From 84f97845b3d9a567cb6c7902978433f5f91091c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Wed, 3 Dec 2025 23:54:41 +0100 Subject: [PATCH 1/2] refactor(lint): Resolve pylint too-many-positional-arguments This commit addresses and resolves several `too-many-positional-arguments` and `unused-argument` linting errors reported by pylint. The changes primarily involve adding `too-many-positional-arguments` to existing `pylint: disable` directives where the function signatures are fixed by external frameworks (Django, SQLAlchemy) or by internal design requirements (e.g., `__new__` methods, `HttpServerRequestEvent` init). Additionally, an `unused-argument` was fixed by renaming a parameter with a leading underscore to indicate its intentional non-use. These modifications ensure that the codebase adheres to the pylint standards without altering the intended functionality or API compatibility. --- _appmap/event.py | 2 +- _appmap/importer.py | 4 +++- _appmap/test/test_configuration.py | 2 +- _appmap/test/test_django.py | 2 +- _appmap/web_framework.py | 6 +++--- appmap/django.py | 4 +++- appmap/pytest.py | 2 +- appmap/sqlalchemy.py | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/_appmap/event.py b/_appmap/event.py index f9c7c490..0094412e 100644 --- a/_appmap/event.py +++ b/_appmap/event.py @@ -446,7 +446,7 @@ class HttpServerRequestEvent(MessageEvent): __slots__ = ["http_server_request"] - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def __init__( self, request_method, diff --git a/_appmap/importer.py b/_appmap/importer.py index 28a6c5a5..4493057c 100644 --- a/_appmap/importer.py +++ b/_appmap/importer.py @@ -42,7 +42,9 @@ class FilterableFn( ): __slots__ = () - def __new__(cls, scope, fn_name, fn, static_fn, auxtype=None): # pylint: disable=too-many-arguments + def __new__( + cls, scope, fn_name, fn, static_fn, auxtype=None + ): # pylint: disable=too-many-arguments,too-many-positional-arguments fqname = "%s.%s" % (scope.fqname, fn_name) self = super(FilterableFn, cls).__new__(cls, scope.scope, fqname, fn, static_fn, auxtype) return self diff --git a/_appmap/test/test_configuration.py b/_appmap/test/test_configuration.py index a1492e4a..aaeb1add 100644 --- a/_appmap/test/test_configuration.py +++ b/_appmap/test/test_configuration.py @@ -269,7 +269,7 @@ def test_missing_packages(self, tmpdir): self.check_default_config(Path(tmpdir).name) class TestSearchConfig: - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def test_config_in_parent_folder(self, data_dir, tmpdir, monkeypatch): copytree(data_dir / "config-up", str(tmpdir), dirs_exist_ok=True) diff --git a/_appmap/test/test_django.py b/_appmap/test/test_django.py index 5547fb9c..072b5628 100644 --- a/_appmap/test/test_django.py +++ b/_appmap/test/test_django.py @@ -98,7 +98,7 @@ def test_template(events): class ClientAdaptor(django.test.Client): """Adaptor for the client request parameters used in .web_framework tests.""" - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def generic( self, method, diff --git a/_appmap/web_framework.py b/_appmap/web_framework.py index 33e27e17..c6edb39b 100644 --- a/_appmap/web_framework.py +++ b/_appmap/web_framework.py @@ -102,7 +102,7 @@ def name_hash(namepart): return sha256(os.fsencode(namepart)).hexdigest() -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments,too-many-positional-arguments def create_appmap_file( output_dir, request_method, @@ -142,7 +142,7 @@ def before_request_main(self, rec, req: Any) -> Tuple[float, int]: """Specify the main operations to be performed by a request is processed.""" raise NotImplementedError - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def after_request_main( self, request_path, status, headers, start, call_event_id ) -> Optional[HttpServerResponseEvent]: @@ -193,7 +193,7 @@ def before_request_hook(self, request) -> Tuple[Optional[Recorder], float, int]: return rec, start, call_event_id - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-positional-arguments def after_request_hook( self, request_path, diff --git a/appmap/django.py b/appmap/django.py index 08674946..420b9bbc 100644 --- a/appmap/django.py +++ b/appmap/django.py @@ -59,7 +59,9 @@ def __init__(self): self.recorder = Recorder.get_current() # This signature is correct, the implementation confuses pylint: - def __call__(self, execute, sql, params, many, context): # pylint: disable=too-many-arguments + def __call__( + self, execute, sql, params, many, context + ): # pylint: disable=too-many-arguments,too-many-positional-arguments start = time.monotonic() try: return execute(sql, params, many, context) diff --git a/appmap/pytest.py b/appmap/pytest.py index 61b655f4..0d20ff8e 100644 --- a/appmap/pytest.py +++ b/appmap/pytest.py @@ -5,7 +5,7 @@ from pytest_django.django_compat import is_django_unittest except ImportError: - def is_django_unittest(item): + def is_django_unittest(_item): return False diff --git a/appmap/sqlalchemy.py b/appmap/sqlalchemy.py index 2e4c382f..66c30d22 100644 --- a/appmap/sqlalchemy.py +++ b/appmap/sqlalchemy.py @@ -13,7 +13,7 @@ @event.listens_for(Engine, "before_cursor_execute") -# pylint: disable=too-many-arguments,unused-argument +# pylint: disable=too-many-arguments,unused-argument,too-many-positional-arguments def capture_sql_call(conn, cursor, statement, parameters, context, executemany): """Capture SQL query call into appmap.""" if is_instrumentation_disabled(): @@ -45,7 +45,7 @@ def capture_sql_call(conn, cursor, statement, parameters, context, executemany): @event.listens_for(Engine, "after_cursor_execute") -# pylint: disable=too-many-arguments,unused-argument +# pylint: disable=too-many-arguments,unused-argument,too-many-positional-arguments def capture_sql(conn, cursor, statement, parameters, context, executemany): """Capture SQL query return into appmap.""" if is_instrumentation_disabled(): From 6196ab1e3f2ab71dc16d78a9bed41423de23577f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Mon, 26 Jan 2026 12:20:54 +0100 Subject: [PATCH 2/2] fix(recording): sanitize process recording filenames for Windows Replace colons in ISO 8601 timestamps with hyphens when generating filenames for process recordings. This prevents OSErrors on Windows systems where colons are invalid characters in filenames. Includes a regression test to verify that generated filenames do not contain colons. Fixes #377 --- _appmap/recording.py | 2 +- _appmap/test/test_recording.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/_appmap/recording.py b/_appmap/recording.py index 8513cbff..4a7d119a 100644 --- a/_appmap/recording.py +++ b/_appmap/recording.py @@ -117,7 +117,7 @@ def save_at_exit(): now = datetime.now(timezone.utc) iso_time = now.isoformat(timespec="seconds").replace("+00:00", "Z") process_id = os.getpid() - appmap_name = f"{iso_time}_{process_id}" + appmap_name = f"{iso_time}-{process_id}".replace(":","-") recorder_type = "process" metadata = { "name": appmap_name, diff --git a/_appmap/test/test_recording.py b/_appmap/test/test_recording.py index d7749d36..5700957c 100644 --- a/_appmap/test/test_recording.py +++ b/_appmap/test/test_recording.py @@ -226,3 +226,27 @@ def test_process_recording(data_dir, shell, tmp_path): actual = json.loads(appmap_files[0].read_text()) assert len(actual["events"]) > 0 assert len(actual["classMap"]) > 0 + + +def test_process_recording_filename_is_sanitized(data_dir, shell, tmp_path): + fixture = data_dir / "package1" + tmp = tmp_path / "process" + copytree(fixture, str(tmp / "package1"), dirs_exist_ok=True) + copy(data_dir / "appmap.yml", str(tmp)) + copytree(data_dir / "flask" / "init", str(tmp / "init"), dirs_exist_ok=True) + + ret = shell.run( + "python", + "-m", + "package1.package2", + env={"PYTHONPATH": "init", "APPMAP_RECORD_PROCESS": "true"}, + cwd=tmp, + ) + assert ret.returncode == 0 + + appmap_dir = tmp / "tmp" / "appmap" / "process" + appmap_files = list(appmap_dir.glob("*.appmap.json")) + assert len(appmap_files) == 1, "this only fails when run from VS Code?" + + filename = appmap_files[0].name + assert ":" not in filename