Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start using pathlib.Path and deprecate sphinx.testing.path #11526

Merged
merged 16 commits into from Jul 27, 2023
5 changes: 5 additions & 0 deletions CHANGES
Expand Up @@ -14,10 +14,15 @@ Deprecated

* #11512: Deprecate ``sphinx.util.md5`` and ``sphinx.util.sha1``.
Use ``hashlib`` instead.
* #11526: Deprecate ``sphinx.testing.path``.
Use ``os.path`` or ``pathlib`` instead.

Features added
--------------

* #11526: Support ``os.PathLike`` types and ``pathlib.Path`` objects
in many more places.

Bugs fixed
----------

Expand Down
5 changes: 5 additions & 0 deletions doc/extdev/deprecated.rst
Expand Up @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- Removed
- Alternatives

* - ``sphinx.testing.path``
- 7.2
- 9.0
- ``os.path`` or ``pathlib``

* - ``sphinx.util.md5``
- 7.2
- 9.0
Expand Down
14 changes: 8 additions & 6 deletions sphinx/application.py
Expand Up @@ -11,6 +11,7 @@
from collections import deque
from io import StringIO
from os import path
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Callable

from docutils import nodes
Expand Down Expand Up @@ -41,7 +42,7 @@
from sphinx.util.display import progress_message
from sphinx.util.i18n import CatalogRepository
from sphinx.util.logging import prefixed_warnings
from sphinx.util.osutil import abspath, ensuredir, relpath
from sphinx.util.osutil import ensuredir, relpath
from sphinx.util.tags import Tags
from sphinx.util.typing import RoleFunction, TitleGetter

Expand Down Expand Up @@ -132,7 +133,8 @@ class Sphinx:
warningiserror: bool
_warncount: int

def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: str,
def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[str] | None,
outdir: str | os.PathLike[str], doctreedir: str | os.PathLike[str],
buildername: str, confoverrides: dict | None = None,
status: IO | None = sys.stdout, warning: IO | None = sys.stderr,
freshenv: bool = False, warningiserror: bool = False,
Expand All @@ -145,9 +147,9 @@ def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: st
self.registry = SphinxComponentRegistry()

# validate provided directories
self.srcdir = abspath(srcdir)
self.outdir = abspath(outdir)
self.doctreedir = abspath(doctreedir)
self.srcdir = Path(srcdir).resolve()
self.outdir = Path(outdir).resolve()
self.doctreedir = Path(doctreedir).resolve()

if not path.isdir(self.srcdir):
raise ApplicationError(__('Cannot find source directory (%s)') %
Expand Down Expand Up @@ -203,7 +205,7 @@ def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: st
self.confdir = self.srcdir
self.config = Config({}, confoverrides or {})
else:
self.confdir = abspath(confdir)
self.confdir = Path(confdir).resolve()
self.config = Config.read(self.confdir, confoverrides or {}, self.tags)

# initialize some limited config variables before initialize i18n and loading
Expand Down
2 changes: 1 addition & 1 deletion sphinx/builders/__init__.py
Expand Up @@ -262,7 +262,7 @@ def build_specific(self, filenames: list[str]) -> None:
filename)
continue

if not filename.startswith(self.srcdir):
if not filename.startswith(str(self.srcdir)):
logger.warning(__('file %r given on command line is not under the '
'source directory, ignoring'), filename)
continue
Expand Down
7 changes: 2 additions & 5 deletions sphinx/builders/_epub_base.py
Expand Up @@ -21,7 +21,7 @@
from sphinx.util import logging
from sphinx.util.display import status_iterator
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.osutil import copyfile, ensuredir
from sphinx.util.osutil import copyfile, ensuredir, relpath

try:
from PIL import Image
Expand Down Expand Up @@ -508,9 +508,6 @@ def build_content(self) -> None:
metadata = self.content_metadata()

# files
if not self.outdir.endswith(os.sep):
self.outdir += os.sep
olen = len(self.outdir)
self.files: list[str] = []
self.ignored_files = ['.buildinfo', 'mimetype', 'content.opf',
'toc.ncx', 'META-INF/container.xml',
Expand All @@ -522,7 +519,7 @@ def build_content(self) -> None:
for root, dirs, files in os.walk(self.outdir):
dirs.sort()
for fn in sorted(files):
filename = path.join(root, fn)[olen:]
filename = relpath(path.join(root, fn), self.outdir)
if filename in self.ignored_files:
continue
ext = path.splitext(filename)[-1]
Expand Down
2 changes: 1 addition & 1 deletion sphinx/builders/changes.py
Expand Up @@ -34,7 +34,7 @@ def init(self) -> None:
self.templates.init(self, self.theme)

def get_outdated_docs(self) -> str:
return self.outdir
return str(self.outdir)

typemap = {
'versionadded': 'added',
Expand Down
6 changes: 4 additions & 2 deletions sphinx/builders/gettext.py
Expand Up @@ -28,6 +28,7 @@
from sphinx.util.template import SphinxRenderer

if TYPE_CHECKING:
import os
from collections.abc import Generator, Iterable

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -84,11 +85,12 @@ def __init__(self, source: str, line: int) -> None:

class GettextRenderer(SphinxRenderer):
def __init__(
self, template_path: str | None = None, outdir: str | None = None,
self, template_path: list[str | os.PathLike[str]] | None = None,
outdir: str | os.PathLike[str] | None = None,
) -> None:
self.outdir = outdir
if template_path is None:
template_path = path.join(package_dir, 'templates', 'gettext')
template_path = [path.join(package_dir, 'templates', 'gettext')]
super().__init__(template_path)

def escape(s: str) -> str:
Expand Down
10 changes: 5 additions & 5 deletions sphinx/builders/html/__init__.py
Expand Up @@ -786,7 +786,7 @@ def copy_image_files(self) -> None:

def copy_download_files(self) -> None:
def to_relpath(f: str) -> str:
return relative_path(self.srcdir, f)
return relative_path(self.srcdir, f) # type: ignore[arg-type]

# copy downloadable files
if self.env.dlfiles:
Expand Down Expand Up @@ -1254,9 +1254,9 @@ def js_tag(js: JavaScript) -> str:
context['js_tag'] = js_tag


def _file_checksum(outdir: str, filename: str) -> str:
def _file_checksum(outdir: str | os.PathLike[str], filename: str | os.PathLike[str]) -> str:
# Don't generate checksums for HTTP URIs
if '://' in filename:
if '://' in str(filename):
return ''
try:
# Ensure universal newline mode is used to avoid checksum differences
Expand Down Expand Up @@ -1305,7 +1305,7 @@ def validate_html_extra_path(app: Sphinx, config: Config) -> None:
logger.warning(__('html_extra_path entry %r does not exist'), entry)
config.html_extra_path.remove(entry)
elif (path.splitdrive(app.outdir)[0] == path.splitdrive(extra_path)[0] and
path.commonpath([app.outdir, extra_path]) == app.outdir):
path.commonpath((app.outdir, extra_path)) == path.normpath(app.outdir)):
logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry)
config.html_extra_path.remove(entry)

Expand All @@ -1318,7 +1318,7 @@ def validate_html_static_path(app: Sphinx, config: Config) -> None:
logger.warning(__('html_static_path entry %r does not exist'), entry)
config.html_static_path.remove(entry)
elif (path.splitdrive(app.outdir)[0] == path.splitdrive(static_path)[0] and
path.commonpath([app.outdir, static_path]) == app.outdir):
path.commonpath((app.outdir, static_path)) == path.normpath(app.outdir)):
logger.warning(__('html_static_path entry %r is placed inside outdir'), entry)
config.html_static_path.remove(entry)

Expand Down
4 changes: 2 additions & 2 deletions sphinx/cmd/build.py
Expand Up @@ -24,7 +24,7 @@
from sphinx.util.console import color_terminal, nocolor, red, terminal_safe # type: ignore
from sphinx.util.docutils import docutils_namespace, patch_docutils
from sphinx.util.exceptions import format_exception_cut_frames, save_traceback
from sphinx.util.osutil import abspath, ensuredir
from sphinx.util.osutil import ensuredir


def handle_exception(
Expand Down Expand Up @@ -234,7 +234,7 @@ def _parse_arguments(argv: list[str] = sys.argv[1:]) -> argparse.Namespace:

if warning and args.warnfile:
try:
warnfile = abspath(args.warnfile)
warnfile = path.abspath(args.warnfile)
ensuredir(path.dirname(warnfile))
warnfp = open(args.warnfile, 'w', encoding="utf-8")
except Exception as exc:
Expand Down
6 changes: 3 additions & 3 deletions sphinx/config.py
Expand Up @@ -21,6 +21,7 @@
from sphinx.util.osutil import _chdir as chdir

if TYPE_CHECKING:
import os
from collections.abc import Generator, Iterator, Sequence

from sphinx.application import Sphinx
Expand Down Expand Up @@ -168,9 +169,8 @@ def __init__(self, config: dict[str, Any] = {}, overrides: dict[str, Any] = {})
self.extensions: list[str] = config.get('extensions', [])

@classmethod
def read(
cls, confdir: str, overrides: dict | None = None, tags: Tags | None = None,
) -> Config:
def read(cls, confdir: str | os.PathLike[str], overrides: dict | None = None,
tags: Tags | None = None) -> Config:
"""Create a Config object from configuration file."""
filename = path.join(confdir, CONFIG_FILENAME)
if not path.isfile(filename):
Expand Down
2 changes: 1 addition & 1 deletion sphinx/directives/code.py
Expand Up @@ -244,7 +244,7 @@ def show_diff(self, location: tuple[str, int] | None = None) -> list[str]:
new_lines = self.read_file(self.filename)
old_filename = self.options['diff']
old_lines = self.read_file(old_filename)
diff = unified_diff(old_lines, new_lines, old_filename, self.filename)
diff = unified_diff(old_lines, new_lines, str(old_filename), str(self.filename))
return list(diff)

def pyobject_filter(
Expand Down
7 changes: 4 additions & 3 deletions sphinx/environment/__init__.py
Expand Up @@ -31,6 +31,7 @@

if TYPE_CHECKING:
from collections.abc import Generator, Iterator
from pathlib import Path

from sphinx.application import Sphinx
from sphinx.builders import Builder
Expand Down Expand Up @@ -147,8 +148,8 @@ class BuildEnvironment:

def __init__(self, app: Sphinx):
self.app: Sphinx = app
self.doctreedir: str = app.doctreedir
self.srcdir: str = app.srcdir
self.doctreedir: Path = app.doctreedir
self.srcdir: Path = app.srcdir
self.config: Config = None # type: ignore[assignment]
self.config_status: int = CONFIG_UNSET
self.config_status_extra: str = ''
Expand Down Expand Up @@ -387,7 +388,7 @@ def merge_info_from(self, docnames: list[str], other: BuildEnvironment,
domain.merge_domaindata(docnames, other.domaindata[domainname])
self.events.emit('env-merge-info', self, docnames, other)

def path2doc(self, filename: str) -> str | None:
def path2doc(self, filename: str | os.PathLike[str]) -> str | None:
"""Return the docname for the filename if the file is document.

*filename* should be absolute or relative to the source directory.
Expand Down
6 changes: 4 additions & 2 deletions sphinx/ext/autosummary/generate.py
Expand Up @@ -424,8 +424,10 @@ def _get_modules(
return public, items


def generate_autosummary_docs(sources: list[str], output_dir: str | None = None,
suffix: str = '.rst', base_path: str | None = None,
def generate_autosummary_docs(sources: list[str],
output_dir: str | os.PathLike[str] | None = None,
suffix: str = '.rst',
base_path: str | os.PathLike[str] | None = None,
imported_members: bool = False, app: Any = None,
overwrite: bool = True, encoding: str = 'utf-8') -> None:
showed_sources = sorted(sources)
Expand Down
7 changes: 5 additions & 2 deletions sphinx/ext/imgmath.py
Expand Up @@ -10,7 +10,7 @@
from hashlib import sha1
from os import path
from subprocess import CalledProcessError
from typing import Any
from typing import TYPE_CHECKING, Any

from docutils import nodes
from docutils.nodes import Element
Expand All @@ -29,6 +29,9 @@
from sphinx.util.template import LaTeXRenderer
from sphinx.writers.html import HTML5Translator

if TYPE_CHECKING:
import os

logger = logging.getLogger(__name__)

templates_path = path.join(package_dir, 'templates', 'imgmath')
Expand Down Expand Up @@ -83,7 +86,7 @@ def write_svg_depth(filename: str, depth: int) -> None:
def generate_latex_macro(image_format: str,
math: str,
config: Config,
confdir: str = '') -> str:
confdir: str | os.PathLike[str] = '') -> str:
"""Generate LaTeX macro."""
variables = {
'fontsize': config.imgmath_font_size,
Expand Down
8 changes: 4 additions & 4 deletions sphinx/project.py
Expand Up @@ -21,7 +21,7 @@
class Project:
"""A project is the source code set of the Sphinx document(s)."""

def __init__(self, srcdir: str, source_suffix: dict[str, str]) -> None:
def __init__(self, srcdir: str | os.PathLike[str], source_suffix: dict[str, str]) -> None:
#: Source directory.
self.srcdir = srcdir

Expand Down Expand Up @@ -61,15 +61,15 @@ def discover(self, exclude_paths: Iterable[str] = (),

return self.docnames

def path2doc(self, filename: str) -> str | None:
def path2doc(self, filename: str | os.PathLike[str]) -> str | None:
"""Return the docname for the filename if the file is a document.

*filename* should be absolute or relative to the source directory.
"""
if filename.startswith(self.srcdir):
if str(filename).startswith(str(self.srcdir)):
filename = relpath(filename, self.srcdir)
for suffix in self.source_suffix:
if filename.endswith(suffix):
if str(filename).endswith(suffix):
filename = path_stabilize(filename)
return filename[:-len(suffix)]

Expand Down