From bf24097a86ed8e9759d16fc39798cacbb2786849 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:16:24 +0000 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ CHANGELOG.md | 1 + audio_library.py | 6 +++--- media_shrinker.py | 14 +++++++------- tests/test_audio_library.py | 22 ++++++++++++---------- tests/test_security.py | 2 +- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 341c7c91..3e8107ad 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -67,3 +67,6 @@ ## 2025-02-12 - [Fast Path Execution in Directory Traversal and Log Parsing] **Learning:** Checking for string existence (`if "silence_" not in stderr`) before invoking regex matchers provides significant speed improvements when parsing large blocks of text. Similarly, moving expensive I/O operations like `os.path.realpath` inside conditional blocks prevents redundant disk access when configuration (like path exclusions) isn't utilized. **Action:** When working on large text processing or disk operations, verify if early exit conditions or conditional execution can bypass the expensive system or library calls. +## 2026-06-26 - [Use os.stat instead of Path.stat() in hot loops] +**Learning:** Calling `pathlib.Path(path).stat()` or `.stat()` on an existing Path object adds significant object instantiation and wrapper overhead in hot loops or batch file operations. Utilizing Python's built-in `os.stat(path)` executes measurably faster, as it natively handles path-like objects directly. +**Action:** Always prefer `os.stat()` for performance-sensitive operations where I/O efficiency is critical. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9313538b..1eeceb62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,4 @@ ### Fixed - 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다. - 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다. +- **성능 개선:** 파일 상태 조회 시 `os.stat()`을 사용하여 객체 생성 오버헤드 감소 및 속도 향상 diff --git a/audio_library.py b/audio_library.py index f3c788a7..ab89e0c6 100644 --- a/audio_library.py +++ b/audio_library.py @@ -574,7 +574,7 @@ def trusted_executable( if stat.S_ISLNK(lexical_metadata.st_mode) and not allow_symlink: raise ValueError(f"trusted executable must not be a symlink: {candidate}") resolved = candidate.resolve(strict=True) - metadata = resolved.stat() + metadata = os.stat(resolved) if not stat.S_ISREG(metadata.st_mode) or not os.access(resolved, os.X_OK): raise ValueError(f"trusted executable is not an executable file: {candidate}") if metadata.st_uid not in {0, os.getuid()}: @@ -1086,7 +1086,7 @@ def _run_stage_json( current_size_rows = [] for partial in staging_dir.glob(pattern): try: - size = partial.stat().st_size + size = os.stat(partial).st_size except FileNotFoundError: # The Rust backend can atomically finalize a partial # between the directory scan and this progress probe. @@ -9188,7 +9188,7 @@ def is_icloud_dataless(path: Path) -> bool: if platform.system() != "Darwin": return False try: - flags = path.stat().st_flags + flags = os.stat(path).st_flags except FileNotFoundError: return False return bool(flags & MACOS_SF_DATALESS) diff --git a/media_shrinker.py b/media_shrinker.py index 8f25f8aa..ae58ee87 100644 --- a/media_shrinker.py +++ b/media_shrinker.py @@ -927,7 +927,7 @@ def preserve_file_attributes( source = Path(source) dest = Path(dest) - source_stat = source.stat() + source_stat = os.stat(source) try: os.chmod(dest, stat.S_IMODE(source_stat.st_mode) & 0o777) @@ -1063,7 +1063,7 @@ def safe_source_size(source: Path) -> int: """Return source size for reports without letting stat failures abort a batch.""" try: - return Path(source).stat().st_size + return os.stat(source).st_size except OSError: return 0 @@ -1089,7 +1089,7 @@ def _find_valid_existing_output( # Fast path: Rely on stat() throwing OSError to check existence and get size simultaneously, # avoiding a redundant exists() syscall. Also defers collision checks for non-existent files. try: - candidate_size = candidate.stat().st_size + candidate_size = os.stat(candidate).st_size except OSError: continue _ensure_not_source_path(source, candidate) @@ -1184,7 +1184,7 @@ def _execute_segment_conversion( overwrite=overwrite, protected_sources=resolved_protected_sources, ) - output_size = first_result.stat().st_size + output_size = os.stat(first_result).st_size if output_size > target_bytes and plan.strategy in { "flac-lossless", "flac-transcode", @@ -1212,7 +1212,7 @@ def _execute_segment_conversion( protected_sources=resolved_protected_sources, ) plan = opus_plan - output_size = first_result.stat().st_size + output_size = os.stat(first_result).st_size try: output_duration = _probe_output_duration( @@ -1393,7 +1393,7 @@ def _remove_invalid_legacy_outputs( # Fast path: Rely on stat() throwing OSError to check existence and get size simultaneously, # avoiding a redundant exists() syscall. Also defers collision checks for non-existent files. try: - legacy_size = legacy_output.stat().st_size + legacy_size = os.stat(legacy_output).st_size except OSError: continue _ensure_not_source_path(source, legacy_output) @@ -2090,7 +2090,7 @@ def _parse_probe_payload( parsed_size = _first_int(format_section.get("size")) if parsed_size is None: parsed_size = ( - source_size if source_size is not None else source_path.stat().st_size + source_size if source_size is not None else os.stat(source_path).st_size ) audio_bit_rate = _first_int( diff --git a/tests/test_audio_library.py b/tests/test_audio_library.py index ae798bbe..e4b42580 100644 --- a/tests/test_audio_library.py +++ b/tests/test_audio_library.py @@ -2884,7 +2884,6 @@ def test_stage_command_decodes_success_and_monitors_progress(self) -> None: vanished = Mock() vanished.name = ".codec-carver-73-1.wav.partial" - vanished.stat.side_effect = FileNotFoundError process = Mock(pid=73, returncode=0) process.communicate.side_effect = [ subprocess.TimeoutExpired(["core", "stage"], 1), @@ -2893,6 +2892,7 @@ def test_stage_command_decodes_success_and_monitors_progress(self) -> None: with ( patch("audio_library.subprocess.Popen", return_value=process), patch("audio_library.Path.glob", side_effect=[[vanished], []]), + patch("audio_library.os.stat", side_effect=FileNotFoundError), patch( "audio_library.time.monotonic", side_effect=[0.0, 0.0, 0.5, 0.5], @@ -8910,17 +8910,19 @@ def test_stream_transcribe_keeps_checkpoint_when_eviction_fails(self) -> None: self.assertFalse(staged.exists()) def test_icloud_dataless_detection(self) -> None: - path = Mock() + path = Mock(spec=Path) with patch("audio_library.platform.system", return_value="Linux"): - self.assertFalse(is_icloud_dataless(path)) - path.stat.assert_not_called() + with patch("audio_library.os.stat") as mock_stat: + self.assertFalse(is_icloud_dataless(path)) + mock_stat.assert_not_called() with patch("audio_library.platform.system", return_value="Darwin"): - path.stat.return_value = Mock(st_flags=audio_library.MACOS_SF_DATALESS) - self.assertTrue(is_icloud_dataless(path)) - path.stat.return_value = Mock(st_flags=0) - self.assertFalse(is_icloud_dataless(path)) - path.stat.side_effect = FileNotFoundError - self.assertFalse(is_icloud_dataless(path)) + with patch("audio_library.os.stat") as mock_stat: + mock_stat.return_value = Mock(st_flags=audio_library.MACOS_SF_DATALESS) + self.assertTrue(is_icloud_dataless(path)) + mock_stat.return_value = Mock(st_flags=0) + self.assertFalse(is_icloud_dataless(path)) + mock_stat.side_effect = FileNotFoundError + self.assertFalse(is_icloud_dataless(path)) def test_staging_capacity_and_safe_cleanup(self) -> None: with tempfile.TemporaryDirectory() as tmp: diff --git a/tests/test_security.py b/tests/test_security.py index 8a814d7d..df488614 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -25,7 +25,7 @@ def test_probe_media_uses_explicit_input_flag_for_dash_prefixed_path( ) source_path = Path("-version.wav") - with patch.object(Path, "stat") as mock_stat: + with patch("media_shrinker.os.stat") as mock_stat: mock_stat.return_value = MagicMock(st_size=10) probe_media(source_path) From e48f8e5b5f0146b8f8f86755645f6ba1cfbdf196 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:39:08 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From b0349df3581032240fd6d4519543b703c1d9d39e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:45:20 +0000 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6e7a3bfe3de4db36c434ec4cca27c8c5c0d3a00e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:10:22 +0000 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From f6a9461e9a5fb6730759b1f638bef4656612404d Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:35:24 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 1e1b13ff8b346b67ee31a2b1186f075fa50db3d4 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 9 Sep 2026 23:02:28 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 3da472f789662d33cfbdb0ff5bc08b5278438213 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 10 Sep 2026 00:19:00 +0000 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From f05287b4cba54cd91355a3fa98749d05e3a7c775 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:29:44 +0000 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8C=8C=EC=9D=BC=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From b3aa175693fe5431421623ab5d6b7a86992b6b0c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:38:08 +0000 Subject: [PATCH 9/9] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20os.stat()=20=EC=B5=9C=EC=A0=81=ED=99=94?= =?UTF-8?q?=20=EB=A1=A4=EB=B0=B1=20(=EC=8B=A4=EC=A7=88=EC=A0=81=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=ED=96=A5=EC=83=81=20=EB=B6=80=EC=A1=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 --- CHANGELOG.md | 1 - audio_library.py | 6 +++--- media_shrinker.py | 14 +++++++------- tests/test_audio_library.py | 22 ++++++++++------------ tests/test_security.py | 2 +- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 3e8107ad..341c7c91 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -67,6 +67,3 @@ ## 2025-02-12 - [Fast Path Execution in Directory Traversal and Log Parsing] **Learning:** Checking for string existence (`if "silence_" not in stderr`) before invoking regex matchers provides significant speed improvements when parsing large blocks of text. Similarly, moving expensive I/O operations like `os.path.realpath` inside conditional blocks prevents redundant disk access when configuration (like path exclusions) isn't utilized. **Action:** When working on large text processing or disk operations, verify if early exit conditions or conditional execution can bypass the expensive system or library calls. -## 2026-06-26 - [Use os.stat instead of Path.stat() in hot loops] -**Learning:** Calling `pathlib.Path(path).stat()` or `.stat()` on an existing Path object adds significant object instantiation and wrapper overhead in hot loops or batch file operations. Utilizing Python's built-in `os.stat(path)` executes measurably faster, as it natively handles path-like objects directly. -**Action:** Always prefer `os.stat()` for performance-sensitive operations where I/O efficiency is critical. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eeceb62..9313538b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,3 @@ ### Fixed - 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다. - 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다. -- **성능 개선:** 파일 상태 조회 시 `os.stat()`을 사용하여 객체 생성 오버헤드 감소 및 속도 향상 diff --git a/audio_library.py b/audio_library.py index ab89e0c6..f3c788a7 100644 --- a/audio_library.py +++ b/audio_library.py @@ -574,7 +574,7 @@ def trusted_executable( if stat.S_ISLNK(lexical_metadata.st_mode) and not allow_symlink: raise ValueError(f"trusted executable must not be a symlink: {candidate}") resolved = candidate.resolve(strict=True) - metadata = os.stat(resolved) + metadata = resolved.stat() if not stat.S_ISREG(metadata.st_mode) or not os.access(resolved, os.X_OK): raise ValueError(f"trusted executable is not an executable file: {candidate}") if metadata.st_uid not in {0, os.getuid()}: @@ -1086,7 +1086,7 @@ def _run_stage_json( current_size_rows = [] for partial in staging_dir.glob(pattern): try: - size = os.stat(partial).st_size + size = partial.stat().st_size except FileNotFoundError: # The Rust backend can atomically finalize a partial # between the directory scan and this progress probe. @@ -9188,7 +9188,7 @@ def is_icloud_dataless(path: Path) -> bool: if platform.system() != "Darwin": return False try: - flags = os.stat(path).st_flags + flags = path.stat().st_flags except FileNotFoundError: return False return bool(flags & MACOS_SF_DATALESS) diff --git a/media_shrinker.py b/media_shrinker.py index ae58ee87..8f25f8aa 100644 --- a/media_shrinker.py +++ b/media_shrinker.py @@ -927,7 +927,7 @@ def preserve_file_attributes( source = Path(source) dest = Path(dest) - source_stat = os.stat(source) + source_stat = source.stat() try: os.chmod(dest, stat.S_IMODE(source_stat.st_mode) & 0o777) @@ -1063,7 +1063,7 @@ def safe_source_size(source: Path) -> int: """Return source size for reports without letting stat failures abort a batch.""" try: - return os.stat(source).st_size + return Path(source).stat().st_size except OSError: return 0 @@ -1089,7 +1089,7 @@ def _find_valid_existing_output( # Fast path: Rely on stat() throwing OSError to check existence and get size simultaneously, # avoiding a redundant exists() syscall. Also defers collision checks for non-existent files. try: - candidate_size = os.stat(candidate).st_size + candidate_size = candidate.stat().st_size except OSError: continue _ensure_not_source_path(source, candidate) @@ -1184,7 +1184,7 @@ def _execute_segment_conversion( overwrite=overwrite, protected_sources=resolved_protected_sources, ) - output_size = os.stat(first_result).st_size + output_size = first_result.stat().st_size if output_size > target_bytes and plan.strategy in { "flac-lossless", "flac-transcode", @@ -1212,7 +1212,7 @@ def _execute_segment_conversion( protected_sources=resolved_protected_sources, ) plan = opus_plan - output_size = os.stat(first_result).st_size + output_size = first_result.stat().st_size try: output_duration = _probe_output_duration( @@ -1393,7 +1393,7 @@ def _remove_invalid_legacy_outputs( # Fast path: Rely on stat() throwing OSError to check existence and get size simultaneously, # avoiding a redundant exists() syscall. Also defers collision checks for non-existent files. try: - legacy_size = os.stat(legacy_output).st_size + legacy_size = legacy_output.stat().st_size except OSError: continue _ensure_not_source_path(source, legacy_output) @@ -2090,7 +2090,7 @@ def _parse_probe_payload( parsed_size = _first_int(format_section.get("size")) if parsed_size is None: parsed_size = ( - source_size if source_size is not None else os.stat(source_path).st_size + source_size if source_size is not None else source_path.stat().st_size ) audio_bit_rate = _first_int( diff --git a/tests/test_audio_library.py b/tests/test_audio_library.py index e4b42580..ae798bbe 100644 --- a/tests/test_audio_library.py +++ b/tests/test_audio_library.py @@ -2884,6 +2884,7 @@ def test_stage_command_decodes_success_and_monitors_progress(self) -> None: vanished = Mock() vanished.name = ".codec-carver-73-1.wav.partial" + vanished.stat.side_effect = FileNotFoundError process = Mock(pid=73, returncode=0) process.communicate.side_effect = [ subprocess.TimeoutExpired(["core", "stage"], 1), @@ -2892,7 +2893,6 @@ def test_stage_command_decodes_success_and_monitors_progress(self) -> None: with ( patch("audio_library.subprocess.Popen", return_value=process), patch("audio_library.Path.glob", side_effect=[[vanished], []]), - patch("audio_library.os.stat", side_effect=FileNotFoundError), patch( "audio_library.time.monotonic", side_effect=[0.0, 0.0, 0.5, 0.5], @@ -8910,19 +8910,17 @@ def test_stream_transcribe_keeps_checkpoint_when_eviction_fails(self) -> None: self.assertFalse(staged.exists()) def test_icloud_dataless_detection(self) -> None: - path = Mock(spec=Path) + path = Mock() with patch("audio_library.platform.system", return_value="Linux"): - with patch("audio_library.os.stat") as mock_stat: - self.assertFalse(is_icloud_dataless(path)) - mock_stat.assert_not_called() + self.assertFalse(is_icloud_dataless(path)) + path.stat.assert_not_called() with patch("audio_library.platform.system", return_value="Darwin"): - with patch("audio_library.os.stat") as mock_stat: - mock_stat.return_value = Mock(st_flags=audio_library.MACOS_SF_DATALESS) - self.assertTrue(is_icloud_dataless(path)) - mock_stat.return_value = Mock(st_flags=0) - self.assertFalse(is_icloud_dataless(path)) - mock_stat.side_effect = FileNotFoundError - self.assertFalse(is_icloud_dataless(path)) + path.stat.return_value = Mock(st_flags=audio_library.MACOS_SF_DATALESS) + self.assertTrue(is_icloud_dataless(path)) + path.stat.return_value = Mock(st_flags=0) + self.assertFalse(is_icloud_dataless(path)) + path.stat.side_effect = FileNotFoundError + self.assertFalse(is_icloud_dataless(path)) def test_staging_capacity_and_safe_cleanup(self) -> None: with tempfile.TemporaryDirectory() as tmp: diff --git a/tests/test_security.py b/tests/test_security.py index df488614..8a814d7d 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -25,7 +25,7 @@ def test_probe_media_uses_explicit_input_flag_for_dash_prefixed_path( ) source_path = Path("-version.wav") - with patch("media_shrinker.os.stat") as mock_stat: + with patch.object(Path, "stat") as mock_stat: mock_stat.return_value = MagicMock(st_size=10) probe_media(source_path)