{"record":{"id":"73c7ef41b5b4d649","repo":"pytest-dev/pytest","slug":"optname-must-be-a-filename-given-path","errorCode":null,"errorMessage":"{optname} must be a filename, given: {path}","messagePattern":"(.+?) must be a filename, given: (.+?)","errorType":"exception","errorClass":"UsageError","httpStatus":null,"severity":"error","filePath":"src/_pytest/config/__init__.py","lineNumber":297,"sourceCode":"\n    from _pytest.deprecated import CONSOLE_MAIN\n\n    warnings.warn(CONSOLE_MAIN, stacklevel=2)\n    return _console_main()\n\n\nclass cmdline:  # compatibility namespace\n    main = staticmethod(main)\n\n\ndef filename_arg(path: str, optname: str) -> str:\n    \"\"\"Argparse type validator for filename arguments.\n\n    :path: Path of filename.\n    :optname: Name of the option.\n    \"\"\"\n    if os.path.isdir(path):\n        raise UsageError(f\"{optname} must be a filename, given: {path}\")\n    return path\n\n\ndef directory_arg(path: str, optname: str) -> str:\n    \"\"\"Argparse type validator for directory arguments.\n\n    :path: Path of directory.\n    :optname: Name of the option.\n    \"\"\"\n    if not os.path.isdir(path):\n        raise UsageError(f\"{optname} must be a directory, given: {path}\")\n    return path\n\n\n# Plugins that cannot be disabled via \"-p no:X\" currently.\nessential_plugins = (\n    \"mark\",\n    \"main\",","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/pytest-dev/pytest/blob/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/config/__init__.py#L279-L315","documentation":"Raised by filename_arg() (src/_pytest/config/__init__.py:290) when the supplied path is a directory. filename_arg is an argparse type validator registered for CLI options that require a plain file (e.g. --junitxml). It raises UsageError (pytest's user-facing error) so the message prints cleanly instead of as a traceback.","triggerScenarios":"Passing a directory path to a pytest CLI option whose argparse type is filename_arg - the validator at config/__init__.py:296-297 fires when os.path.isdir(path) is True.","commonSituations":"Running `pytest --junitxml=reports/` where reports/ exists as a directory; passing a directory to --resultlog, --cachefile, or any plugin option wired through filename_arg.","solutions":["Point the option at a file path (e.g. --junitxml=reports/junit.xml); pytest will create it","If you meant to set the output directory, use the directory-specific option (e.g. --basetemp uses directory_arg)","Remove the trailing slash from the argument if you accidentally turned a file name into a directory"],"exampleFix":"// before\npytest --junitxml=reports/\n// after\npytest --junitxml=reports/junit.xml","handlingStrategy":"validation","validationCode":"import os\nif os.path.isdir(path):\n    raise ValueError(f'{optname} expects a file, {path} is a directory')","typeGuard":"def is_file_not_dir(p: str) -> bool:\n    import os\n    return os.path.exists(p) and not os.path.isdir(p)","tryCatchPattern":"from _pytest.config import filename_arg\ntry:\n    p = filename_arg(user_input, 'opt')\nexcept UsageError as e:\n    print(e)","preventionTips":["Validate file-output paths with os.path.isfile/os.path.isdir before passing to pytest","Strip trailing slashes from intended file paths in CI scripts","Differentiate file-output options (--junitxml) from directory options (--rootdir) in wrapper scripts"],"tags":["cli","argparse","config","validation"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}