"""Mock moviepy modules for all moviepy_showcase tests. This module-level setup installs mock moviepy packages into sys.modules so source modules can be imported without moviepy installed. """ from __future__ import annotations import importlib import sys from typing import Any from unittest.mock import MagicMock import numpy as np import pytest _H, _W = 1080, 1920 def create_mock_clip(**overrides: float | tuple[int, int]) -> MagicMock: """Return a MagicMock that behaves enough like a moviepy clip.""" clip = MagicMock() clip.duration = overrides.get("duration", 2.0) clip.size = overrides.get("size", (_W, _H)) clip.fps = overrides.get("fps", 30) chain = [ "with_fps", "with_duration", "with_position", "with_opacity", "with_mask", "with_audio", "with_effects", "with_background_color", "with_speed_scaled", "with_section_cut_out", "with_effects_on_subclip", "with_layer_index", "with_volume_scaled", "with_start", "subclipped", "cropped", "resized", "rotated", "image_transform", "transform", "time_transform", "to_ImageClip", "to_mask", "to_RGB", ] for name in chain: getattr(clip, name).return_value = clip return clip # ── Build mock module tree ──────────────────────────────────────── mock_moviepy = MagicMock() _clip_classes = [ "VideoClip", "ColorClip", "TextClip", "ImageClip", "CompositeVideoClip", "VideoFileClip", "BitmapClip", "DataVideoClip", "ImageSequenceClip", "AudioClip", "AudioArrayClip", "CompositeAudioClip", ] _drawing_shape = (_H, _W) def _reset_side_effects() -> None: """(Re)apply side_effect / return_value on the shared mock_moviepy.""" for cls_name in _clip_classes: getattr(mock_moviepy, cls_name).side_effect = lambda *_a, **_kw: ( create_mock_clip() ) mock_moviepy.concatenate_videoclips.side_effect = lambda *_a, **_kw: ( create_mock_clip() ) mock_moviepy.concatenate_audioclips.side_effect = lambda *_a, **_kw: ( create_mock_clip() ) mock_moviepy.video.compositing.CompositeVideoClip.clips_array.side_effect = ( lambda *_a, **_kw: create_mock_clip() ) # Drawing tools must return real numpy arrays (used in numpy ops) mock_moviepy.video.tools.drawing.circle.return_value = np.zeros( _drawing_shape, dtype=np.float64 ) mock_moviepy.video.tools.drawing.color_gradient.return_value = np.zeros( _drawing_shape, dtype=np.float64 ) mock_moviepy.video.tools.drawing.color_split.return_value = np.zeros( _drawing_shape, dtype=np.float64 ) _reset_side_effects() # ── Install into sys.modules ───────────────────────────────────── _module_paths = [ "moviepy", "moviepy.video", "moviepy.video.fx", "moviepy.video.compositing", "moviepy.video.compositing.CompositeVideoClip", "moviepy.video.tools", "moviepy.video.tools.drawing", "moviepy.audio", "moviepy.audio.fx", ] def _install_moviepy_mocks() -> None: """(Re)install this conftest's moviepy mocks into sys.modules.""" for _mod in _module_paths: parts = _mod.split(".") obj: Any = mock_moviepy for part in parts[1:]: obj = getattr(obj, part) sys.modules[_mod] = obj _install_moviepy_mocks() _source_modules = [ "python_pkg.moviepy_showcase.moviepy_showcase", "python_pkg.moviepy_showcase._moviepy_clip_types", "python_pkg.moviepy_showcase._moviepy_video_effects", "python_pkg.moviepy_showcase._moviepy_audio_output", ] @pytest.fixture(autouse=True) def _reinstall_moviepy_mocks() -> None: """Ensure our moviepy mocks are active even if another conftest overwrote.""" _install_moviepy_mocks() _reset_side_effects() # Re-bind cached ``from moviepy import X`` references in source modules # that may point to stale MagicMock children from a prior sys.modules entry. for mod_name in _source_modules: if mod_name in sys.modules: importlib.reload(sys.modules[mod_name])