def test_load_list_dict_type(): """Fails on ruamel.yaml<0.15.70 (CommentedMap/CommentedSeq).""" dic = yaml.load("{}") assert isinstance(dic, dict) lis = yaml.load("[]") assert isinstance(lis, list)
def test_dump_load_aliases(): """ Ensure dumping and loading `xx=Alias('x')` works. Ensure loading `{x=1, xx=1}` raises an error. Does not check constructor `Config(xx=1)`.""" class Config(DumpableAttrs, kw_only=False): x: int xx = Alias("x") # Test dumping assert len(attr.fields(Config)) == 1 cfg = Config(1) s = yaml.dump(cfg) assert (s == """\ !Config x: 1 """) assert yaml.load(s) == cfg # Test loading s = """\ !Config xx: 1 """ assert yaml.load(s) == Config(x=1) # Test exception on duplicated parameters. s = """\ !Config x: 1 xx: 1 """ with pytest.raises(CorrError): yaml.load(s)
def test_load_trigger_config(): from corrscope.config import yaml # Ensure no exceptions yaml.load("""\ !CorrelationTriggerConfig trigger_strength: 3 responsiveness: 0.2 falloff_width: 2 """)
def test_load_argument_validation(): """ Ensure that loading config via YAML catches missing parameters. """ class Config(DumpableAttrs): a: int yaml.load("""\ !Config a: 1 """) with pytest.raises(TypeError): yaml.load("!Config {}")
def test_skip_dump_load(): """Ensure _fields or init=False are not dumped, and don't crash on loading. Ensure that init=False fields are not retrieved at all. """ class Foo(DumpableAttrs, always_dump="*"): _underscore: int _underscore_default: int = 1 init_false: int = attr.ib(init=False) never_initialized: int = attr.ib(init=False) def __attrs_post_init__(self): self.init_false = 1 # Ensure fields are not dumped. foo = Foo(underscore=1) assert (yaml.dump(foo) == """\ !Foo underscore: 1 underscore_default: 1 """) # Ensure init=False fields don't crash on loading. evil = """\ !Foo underscore: 1 _underscore: 2 init_false: 3 """ assert yaml.load(evil)._underscore == 1
def test_ignore_unrecognized_fields(): """Ensure unrecognized fields yield warning, not exception.""" class Foo(DumpableAttrs): foo: int s = """\ !Foo foo: 1 bar: 2 """ with pytest.warns(CorrWarning): assert yaml.load(s) == Foo(1)
def test_dump_load_ignored(): """ Ensure loading `xx=Ignored` works. Does not check constructor `Config(xx=1)`. """ class Config(DumpableAttrs): xx = Ignored # Test dumping assert len(attr.fields(Config)) == 0 cfg = Config() s = yaml.dump(cfg) assert (s == """\ !Config {} """) assert yaml.load(s) == cfg # Test loading s = """\ !Config xx: 1 """ assert yaml.load(s) == Config()
def test_loading_corrupted_locale_config(): """ See corrscope.config.MyYAML.load() docstring. """ runner = click.testing.CliRunner() with runner.isolated_filesystem(): raw_yaml = YAML() p = Path("test.yaml") before = {"key": "á"} raw_yaml.dump(before, p) after = yaml.load(p) assert after == before
def test_load_post_init(): """ yaml.load() does not natively call __init__. So DumpableAttrs modifies __setstate__ to call __attrs_post_init__. """ class Foo(DumpableAttrs): foo: int def __attrs_post_init__(self): self.foo = 99 s = """\ !Foo foo: 0 """ assert yaml.load(s) == Foo(99)
def test_load_dump_dict_order(): s = """\ a: 1 b: 1 c: 1 d: 1 e: 1 z: 1 y: 1 x: 1 w: 1 v: 1 """ dic = yaml.load(s) assert yaml.dump(dic) == s, yaml.dump(dic)
def load_cfg_from_path(self, cfg_path: Path): # Bind GUI to dummy config, in case loading cfg_path raises Exception. if self.model is None: self.load_cfg(template_config(), None) assert cfg_path.is_file() self.pref.file_dir = str(cfg_path.parent.resolve()) # Raises YAML structural exceptions cfg = yaml.load(cfg_path) try: # Raises color getter exceptions self.load_cfg(cfg, cfg_path) except Exception as e: # FIXME if error halfway, clear "file path" and load empty model. TracebackDialog(self).showMessage(format_stack_trace(e)) return
def load_cfg_from_path(self, cfg_path: Path): # Bind GUI to dummy config, in case loading cfg_path raises Exception. if self.model is None: self.load_cfg(default_config(), None) assert cfg_path.is_file() self.pref.file_dir = str(cfg_path.parent.resolve()) try: # Raises YAML structural exceptions cfg = yaml.load(cfg_path) # Raises color getter exceptions # FIXME if error halfway, clear "file path" and load empty model. self.load_cfg(cfg, cfg_path) except Exception as e: qw.QMessageBox.critical(self, "Error loading file", str(e)) return
def test_unicode_dump_load(): """ Crashes on latest ruamel.yaml 0.16.5. https://bitbucket.org/ruamel/yaml/issues/316/unicode-encoding-decoding-errors-on And affects real users when they save "most recent files" containing Unicode. https://github.com/jimbo1qaz/corrscope/issues/308 Workaround in MyYAML.dump(), to encode as UTF-8 instead of locale. """ runner = click.testing.CliRunner() with runner.isolated_filesystem(): p = Path("test.yaml") before = {"key": "á😂"} yaml.dump(before, p) after = yaml.load(p) assert after == before
def yaml_sink(_mocker, command: str): """ Mocks yaml.dump() and returns call args. Also tests dumping and loading. """ with mock.patch.object(yaml, "dump") as dump: argv = shlex.split(command) + ["-w"] call_main(argv) dump.assert_called_once() (cfg, stream), kwargs = dump.call_args assert isinstance(cfg, Config) yaml_dump = yaml.dump(cfg) # YAML Representer.ignore_aliases() should return True for Enums. # If it returns True for other types, "null" is dumped explicitly, which is ugly. assert "end_time: null" not in yaml_dump cfg_round_trip = yaml.load(yaml_dump) assert cfg_round_trip == cfg, yaml_dump return (cfg, stream)
def load_prefs() -> GlobalPrefs: try: return yaml.load(_PREF_PATH) except FileNotFoundError: return GlobalPrefs()
def test_list_slice_assign(): """Crashes on ruamel.yaml<0.15.55 (CommentedSeq).""" lis = yaml.load("[]") lis[0:0] = list(range(5)) lis[2:5] = []
def main( files: Tuple[str], # cfg audio: Optional[str], # gui write: bool, play: bool, render: bool, profile: bool, ): """Intelligent oscilloscope visualizer for .wav files. FILES can be one or more .wav files (or wildcards), one folder, or one .yaml config. """ # GUI: # corrscope # corrscope file.yaml # corrscope wildcard/wav/folder ... [--options] # # CLI: # corrscope wildcard/wav/folder ... [--options] --write-cfg file.yaml [--play] # corrscope wildcard/wav/folder ... --play # corrscope file.yaml --play # corrscope file.yaml --write-yaml # # - You can specify as many wildcards or wav files as you want. # - You can only supply one folder, with no files/wildcards. show_gui = not any([write, play, render]) # Gather data for cfg: Config object. CfgOrPath = Union[Config, Path] cfg_or_path: Union[Config, Path, None] = None cfg_dir: Optional[str] = None wav_list: List[Path] = [] for name in files: path = Path(name) # Windows likes to raise OSError when path contains * try: is_dir = path.is_dir() except OSError: is_dir = False if is_dir: # Add a directory. if len(files) > 1: # Warning is technically optional, since wav_prefix has been removed. raise click.ClickException( f'Cannot supply multiple arguments when providing folder {path}' ) matches = sorted(path.glob('*.wav')) wav_list += matches break elif path.suffix in YAML_EXTS: # Load a YAML file to cfg, and skip default_config(). if len(files) > 1: raise click.ClickException( f'Cannot supply multiple arguments when providing config {path}' ) cfg_or_path = path cfg_dir = str(path.parent) break else: # Load one or more wav files. matches = sorted(Path().glob(name)) if not matches: matches = [path] if not path.exists(): raise click.ClickException( f'Supplied nonexistent file or wildcard: {path}') wav_list += matches if not cfg_or_path: # cfg and cfg_dir are always initialized together. channels = [ChannelConfig(str(wav_path)) for wav_path in wav_list] cfg_or_path = default_config( master_audio=audio, # fps=default, channels=channels, # width_ms...trigger=default, # amplification...render=default, ) cfg_dir = '.' assert cfg_or_path is not None assert cfg_dir is not None if show_gui: def command(): from corrscope import gui return gui.gui_main(cast(CfgOrPath, cfg_or_path)) if profile: import cProfile # Pycharm can't load CProfile files with dots in the name. profile_dump_name = get_profile_dump_name('gui') cProfile.runctx('command()', globals(), locals(), profile_dump_name) else: command() else: if not files: raise click.UsageError('Must specify files or folders to play') if isinstance(cfg_or_path, Config): cfg = cfg_or_path cfg_path = None elif isinstance(cfg_or_path, Path): cfg = yaml.load(cfg_or_path) cfg_path = cfg_or_path else: assert False, cfg_or_path if write: write_path = get_path(audio, YAML_NAME) yaml.dump(cfg, write_path) outputs = [] # type: List[IOutputConfig] if play: outputs.append(FFplayOutputConfig()) if render: video_path = get_path(cfg_path or audio, VIDEO_NAME) outputs.append(FFmpegOutputConfig(video_path)) if outputs: arg = Arguments(cfg_dir=cfg_dir, outputs=outputs) command = lambda: CorrScope(cfg, arg).play() if profile: import cProfile # Pycharm can't load CProfile files with dots in the name. first_song_name = Path(files[0]).name.split('.')[0] profile_dump_name = get_profile_dump_name(first_song_name) cProfile.runctx('command()', globals(), locals(), profile_dump_name) else: try: command() except MissingFFmpegError as e: # Tell user how to install ffmpeg (__str__). print(e, file=sys.stderr)