Skip to content

Permission denied error when sphinx template files are read-only #54

@apteryks

Description

@apteryks

Hi,

I found this issue via the test suite, and traced it to the fact that the source files copied by sphinx itself when instantiating the Sphinx object (sphinx_app in sphinxify.py) were read-only. The error looks like:

___________________________ test_sphinxify[outline] ____________________________

build_oinfo = <function fixture_build_oinfo.<locals>._build_oinfo at 0x7fffd551a1f0>
set_docrepr_options = <function fixture_set_docrepr_options.<locals>._set_docrepr_options at 0x7fffd551ae50>
open_browser = <function open_browser.<locals>._open_browser at 0x7fffd54de310>
compare_screenshots = <function compare_screenshots.<locals>._compare_screenshots at 0x7fffd54de4c0>
test_id = 'outline', obj = <ufunc 'sin'>, oinfo_data = {'name': 'sin'}
docrepr_options = {'outline': True}

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        ('test_id', 'obj', 'oinfo_data', 'docrepr_options'),
        _test_cases_to_params(TEST_CASES),
        ids=list(TEST_CASES.keys()),
        )
    async def test_sphinxify(
            build_oinfo, set_docrepr_options, open_browser, compare_screenshots,
            test_id, obj, oinfo_data, docrepr_options,
            ):
        """Test the operation of the Sphinxify module on various docstrings."""
        if (oinfo_data.get('docstring', None) == PLOT_DOCSTRING
                and sys.version_info.major == 3
                and sys.version_info.minor == 6
                and sys.platform.startswith('win')):
            pytest.skip(
                'Plot fails on Py3.6 on Windows; older version of Matplotlib?')
    
        oinfo = build_oinfo(obj, **oinfo_data)
        set_docrepr_options(**docrepr_options)
    
>       url = docrepr.sphinxify.rich_repr(oinfo)

docrepr/tests/test_output.py:240: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/gnu/store/dmcslfpdfyv74mfwwbqliksr255xk8iw-python-docrepr-0.2.0/lib/python3.9/site-packages/docrepr/sphinxify.py:450: in rich_repr
    class_doc = sphinxify(wrapped_class_docstring, srcdir)
/gnu/store/dmcslfpdfyv74mfwwbqliksr255xk8iw-python-docrepr-0.2.0/lib/python3.9/site-packages/docrepr/sphinxify.py:405: in sphinxify
    merge_directories(destdir, srcdir)
/gnu/store/dmcslfpdfyv74mfwwbqliksr255xk8iw-python-docrepr-0.2.0/lib/python3.9/site-packages/docrepr/utils.py:52: in merge_directories
    shutil.copytree(source, destination, dirs_exist_ok=True)
/gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/shutil.py:566: in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

entries = [<DirEntry 'docstring.html'>, <DirEntry 'search.html'>, <DirEntry '_static'>, <DirEntry '.buildinfo'>, <DirEntry 'searchindex.js'>, <DirEntry 'objects.inv'>]
src = '/tmp/guix-build-python-docrepr-0.2.0.drv-0/tmpev83mper'
dst = '/tmp/guix-build-python-docrepr-0.2.0.drv-0/docrepr-None/tmpbm5c0r1l'
symlinks = False, ignore = None
copy_function = <function copy2 at 0x7ffff73c5d30>
ignore_dangling_symlinks = False, dirs_exist_ok = True

    def _copytree(entries, src, dst, symlinks, ignore, copy_function,
                  ignore_dangling_symlinks, dirs_exist_ok=False):
        if ignore is not None:
            ignored_names = ignore(os.fspath(src), [x.name for x in entries])
        else:
            ignored_names = set()
    
        os.makedirs(dst, exist_ok=dirs_exist_ok)
        errors = []
        use_srcentry = copy_function is copy2 or copy_function is copy
    
        for srcentry in entries:
            if srcentry.name in ignored_names:
                continue
            srcname = os.path.join(src, srcentry.name)
            dstname = os.path.join(dst, srcentry.name)
            srcobj = srcentry if use_srcentry else srcname
            try:
                is_symlink = srcentry.is_symlink()
                if is_symlink and os.name == 'nt':
                    # Special check for directory junctions, which appear as
                    # symlinks but we want to recurse.
                    lstat = srcentry.stat(follow_symlinks=False)
                    if lstat.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT:
                        is_symlink = False
                if is_symlink:
                    linkto = os.readlink(srcname)
                    if symlinks:
                        # We can't just leave it to `copy_function` because legacy
                        # code with a custom `copy_function` may rely on copytree
                        # doing the right thing.
                        os.symlink(linkto, dstname)
                        copystat(srcobj, dstname, follow_symlinks=not symlinks)
                    else:
                        # ignore dangling symlink if the flag is on
                        if not os.path.exists(linkto) and ignore_dangling_symlinks:
                            continue
                        # otherwise let the copy occur. copy2 will raise an error
                        if srcentry.is_dir():
                            copytree(srcobj, dstname, symlinks, ignore,
                                     copy_function, dirs_exist_ok=dirs_exist_ok)
                        else:
                            copy_function(srcobj, dstname)
                elif srcentry.is_dir():
                    copytree(srcobj, dstname, symlinks, ignore, copy_function,
                             dirs_exist_ok=dirs_exist_ok)
                else:
                    # Will raise a SpecialFileError for unsupported file types
                    copy_function(srcobj, dstname)
            # catch the Error from the recursive copytree so that we can
            # continue with other files
            except Error as err:
                errors.extend(err.args[0])
            except OSError as why:
                errors.append((srcname, dstname, str(why)))
        try:
            copystat(src, dst)
        except OSError as why:
            # Copying file access times may fail on Windows
            if getattr(why, 'winerror', None) is None:
                errors.append((src, dst, str(why)))
        if errors:
>           raise Error(errors)
E           shutil.Error: [('/tmp/guix-build-python-docrepr-0.2.0.drv-0/tmpev83mper/_static/plot_directive.css', '/tmp/guix-build-python-docrepr-0.2.0.drv-0/docrepr-None/tmpbm5c0r1l/_static/plot_directive.css', "[Errno 13] Permission denied: '/tmp/guix-build-python-docrepr-0.2.0.drv-0/docrepr-None/tmpbm5c0r1l/_static/plot_directive.css'")]

/gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/shutil.py:522: Error

I've fixed it by changing except TypeError to except (TypeError, shutil.Error) (which means the copying falls back to the manual routine below the shutil.copytree failed call). A better approach could be to ensure all files are writable after creating the Sphinx object.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions