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

Encoding warning use PEP 597 env var PYTHONWARNDEFAULTENCODING #733

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
Expand Down
6 changes: 1 addition & 5 deletions src/monty/bisect.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
from __future__ import annotations

import bisect as bs
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Optional

__author__ = "Matteo Giantomassi"
__copyright__ = "Copyright 2013, The Materials Virtual Lab"
Expand All @@ -23,7 +19,7 @@
__date__ = "11/09/14"


def index(a: list[float], x: float, atol: Optional[float] = None) -> int:
def index(a: list[float], x: float, atol: float | None = None) -> int:
"""Locate the leftmost value exactly equal to x."""
i = bs.bisect_left(a, x)
if i != len(a):
Expand Down
8 changes: 4 additions & 4 deletions src/monty/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Callable, Optional, Type
from typing import Callable, Type

logger = logging.getLogger(__name__)


def deprecated(
replacement: Optional[Callable | str] = None,
replacement: Callable | str | None = None,
message: str = "",
deadline: Optional[tuple[int, int, int]] = None,
deadline: tuple[int, int, int] | None = None,
category: Type[Warning] = FutureWarning,
) -> Callable:
"""
Expand All @@ -34,7 +34,7 @@ def deprecated(
Args:
replacement (Callable | str): A replacement class or function.
message (str): A warning message to be displayed.
deadline (Optional[tuple[int, int, int]]): Optional deadline for removal
deadline (tuple[int, int, int] | None): Optional deadline for removal
of the old function/class, in format (yyyy, MM, dd). A CI warning would
be raised after this date if is running in code owner' repo.
category (Warning): Choose the category of the warning to issue. Defaults
Expand Down
4 changes: 2 additions & 2 deletions src/monty/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any, Callable, Union
from typing import Any, Callable


class _HashedSeq(list): # pylint: disable=C0205
Expand Down Expand Up @@ -130,7 +130,7 @@ def invalidate(cls, inst: object, name: str) -> None:


def return_if_raise(
exception_tuple: Union[list, tuple], retval_if_exc: Any, disabled: bool = False
exception_tuple: list | tuple, retval_if_exc: Any, disabled: bool = False
) -> Any:
"""
Decorator for functions, methods or properties. Execute the callable in a
Expand Down
18 changes: 10 additions & 8 deletions src/monty/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@
from typing import TYPE_CHECKING, Literal, cast

if TYPE_CHECKING:
from typing import IO, Any, Iterator, Union


class EncodingWarning(Warning): ... # Added in Python 3.10
from typing import IO, Any, Iterator


def zopen(
filename: Union[str, Path],
filename: str | Path,
/,
mode: str | None = None,
**kwargs: Any,
Expand Down Expand Up @@ -79,8 +76,13 @@ def zopen(
stacklevel=2,
)

# Warn against default `encoding` in text mode
if "t" in mode and kwargs.get("encoding", None) is None:
# Warn against default `encoding` in text mode if
# `PYTHONWARNDEFAULTENCODING` environment variable is set (PEP 597)
if (
"t" in mode
and kwargs.get("encoding", None) is None
and os.getenv("PYTHONWARNDEFAULTENCODING", False)
):
warnings.warn(
"We strongly encourage explicit `encoding`, "
"and we would use UTF-8 by default as per PEP 686",
Expand Down Expand Up @@ -169,7 +171,7 @@ def _get_line_ending(


def reverse_readfile(
filename: Union[str, Path],
filename: str | Path,
) -> Iterator[str]:
"""
A much faster reverse read of file by using Python's mmap to generate a
Expand Down
6 changes: 3 additions & 3 deletions src/monty/os/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

if TYPE_CHECKING:
from pathlib import Path
from typing import Generator, Union
from typing import Generator

__author__ = "Shyue Ping Ong"
__copyright__ = "Copyright 2013, The Materials Project"
Expand All @@ -22,7 +22,7 @@


@contextmanager
def cd(path: Union[str, Path]) -> Generator:
def cd(path: str | Path) -> Generator:
"""
A Fabric-inspired cd context that temporarily changes directory for
performing some tasks, and returns to the original working directory
Expand All @@ -42,7 +42,7 @@ def cd(path: Union[str, Path]) -> Generator:
os.chdir(cwd)


def makedirs_p(path: Union[str, Path], **kwargs) -> None:
def makedirs_p(path: str | Path, **kwargs) -> None:
"""
Wrapper for os.makedirs that does not raise an exception if the directory
already exists, in the fashion of "mkdir -p" command. The check is
Expand Down
8 changes: 4 additions & 4 deletions src/monty/os/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from monty.string import list_strings

if TYPE_CHECKING:
from typing import Callable, Literal, Optional, Union
from typing import Callable, Literal


def zpath(filename: str | Path) -> str:
Expand Down Expand Up @@ -41,9 +41,9 @@ def zpath(filename: str | Path) -> str:

def find_exts(
top: str,
exts: Union[str, list[str]],
exclude_dirs: Optional[str] = None,
include_dirs: Optional[str] = None,
exts: str | list[str],
exclude_dirs: str | None = None,
include_dirs: str | None = None,
match_mode: Literal["basename", "abspath"] = "basename",
) -> list[str]:
"""
Expand Down
24 changes: 18 additions & 6 deletions src/monty/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@

if TYPE_CHECKING:
from pathlib import Path
from typing import Any, Optional, TextIO, Union
from typing import Any, Literal, TextIO


def loadfn(fn: Union[str, Path], *args, fmt: Optional[str] = None, **kwargs) -> Any:
def loadfn(
fn: str | Path,
*args,
fmt: Literal["json", "yaml", "mpk"] | None = None,
**kwargs,
) -> Any:
"""
Loads json/yaml/msgpack directly from a filename instead of a
File-like object. File may also be a BZ2 (".BZ2") or GZIP (".GZ", ".Z")
Expand All @@ -39,9 +44,8 @@ def loadfn(fn: Union[str, Path], *args, fmt: Optional[str] = None, **kwargs) ->
Args:
fn (str/Path): filename or pathlib.Path.
*args: Any of the args supported by json/yaml.load.
fmt (string): If specified, the fmt specified would be used instead
of autodetection from filename. Supported formats right now are
"json", "yaml" or "mpk".
fmt ("json" | "yaml" | "mpk"): If specified, the fmt specified would
be used instead of autodetection from filename.
**kwargs: Any of the kwargs supported by json/yaml.load.

Returns:
Expand Down Expand Up @@ -81,7 +85,13 @@ def loadfn(fn: Union[str, Path], *args, fmt: Optional[str] = None, **kwargs) ->
raise TypeError(f"Invalid format: {fmt}")


def dumpfn(obj: object, fn: Union[str, Path], *args, fmt=None, **kwargs) -> None:
def dumpfn(
obj: object,
fn: str | Path,
*args,
fmt: Literal["json", "yaml", "mpk"] | None = None,
**kwargs,
) -> None:
"""
Dump to a json/yaml directly by filename instead of a
File-like object. File may also be a BZ2 (".BZ2") or GZIP (".GZ", ".Z")
Expand All @@ -95,6 +105,8 @@ def dumpfn(obj: object, fn: Union[str, Path], *args, fmt=None, **kwargs) -> None
Args:
obj (object): Object to dump.
fn (str/Path): filename or pathlib.Path.
fmt ("json" | "yaml" | "mpk"): If specified, the fmt specified would
be used instead of autodetection from filename.
*args: Any of the args supported by json/yaml.dump.
**kwargs: Any of the kwargs supported by json/yaml.dump.

Expand Down
6 changes: 3 additions & 3 deletions src/monty/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from monty.io import zopen

if TYPE_CHECKING:
from typing import Literal, Optional
from typing import Literal


def copy_r(src: str | Path, dst: str | Path) -> None:
Expand Down Expand Up @@ -76,7 +76,7 @@ def gzip_dir(path: str | Path, compresslevel: int = 6) -> None:
def compress_file(
filepath: str | Path,
compression: Literal["gz", "bz2"] = "gz",
target_dir: Optional[str | Path] = None,
target_dir: str | Path | None = None,
) -> None:
"""
Compresses a file with the correct extension. Functions like standard
Expand Down Expand Up @@ -130,7 +130,7 @@ def compress_dir(path: str | Path, compression: Literal["gz", "bz2"] = "gz") ->


def decompress_file(
filepath: str | Path, target_dir: Optional[str | Path] = None
filepath: str | Path, target_dir: str | Path | None = None
) -> str | None:
"""
Decompresses a file with the correct extension. Automatically detects
Expand Down
4 changes: 2 additions & 2 deletions src/monty/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import TYPE_CHECKING, Iterable, cast

if TYPE_CHECKING:
from typing import Any, Union
from typing import Any


def remove_non_ascii(s: str) -> str:
Expand All @@ -34,7 +34,7 @@ def is_string(s: Any) -> bool:
return False


def list_strings(arg: Union[str, Iterable[str]]) -> list[str]:
def list_strings(arg: str | Iterable[str]) -> list[str]:
"""
Always return a list of strings, given a string or list of strings as
input.
Expand Down
4 changes: 1 addition & 3 deletions src/monty/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from monty.string import is_string

if TYPE_CHECKING:
from typing import Optional

from typing_extensions import Self

__author__ = "Matteo Giantomass"
Expand Down Expand Up @@ -63,7 +61,7 @@ def __init__(self, command: str):
def __str__(self):
return f"command: {self.command}, retcode: {self.retcode}"

def run(self, timeout: Optional[float] = None, **kwargs) -> Self:
def run(self, timeout: float | None = None, **kwargs) -> Self:
"""
Run a command in a separated thread and wait timeout seconds.
kwargs are keyword arguments passed to Popen.
Expand Down
5 changes: 1 addition & 4 deletions src/monty/tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

from monty.shutil import copy_r, gzip_dir, remove

if TYPE_CHECKING:
from typing import Union


class ScratchDir:
"""
Expand Down Expand Up @@ -42,7 +39,7 @@ class ScratchDir:

def __init__(
self,
rootpath: Union[str, Path, None],
rootpath: str | Path | None,
create_symbolic_link: bool = False,
copy_from_current_on_enter: bool = False,
copy_to_current_on_exit: bool = False,
Expand Down
19 changes: 17 additions & 2 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import pytest

from monty.io import (
EncodingWarning,
FileLock,
FileLockException,
_get_line_ending,
Expand Down Expand Up @@ -427,23 +426,39 @@ def test_lzw_files(self):
# Cannot decompress a real LZW file
with (
pytest.raises(gzip.BadGzipFile, match="Not a gzipped file"),
pytest.warns(FutureWarning, match="compress LZW-compressed files"),
zopen(f"{TEST_DIR}/real_lzw_file.txt.Z", "rt", encoding="utf-8") as f,
):
f.read()

@pytest.mark.parametrize("extension", [".txt", ".bz2", ".gz", ".xz", ".lzma"])
def test_warnings(self, extension):
def test_warnings(self, extension, monkeypatch):
filename = f"test_warning{extension}"
content = "Test warning"

with ScratchDir("."):
monkeypatch.setenv("PYTHONWARNDEFAULTENCODING", "1")

# Default `encoding` warning
with (
pytest.warns(EncodingWarning, match="use UTF-8 by default"),
DanielYang59 marked this conversation as resolved.
Show resolved Hide resolved
zopen(filename, "wt") as f,
):
f.write(content)

# No encoding warning if `PYTHONWARNDEFAULTENCODING` not set
monkeypatch.delenv("PYTHONWARNDEFAULTENCODING", raising=False)

with warnings.catch_warnings():
warnings.filterwarnings(
"error",
"We strongly encourage explicit `encoding`",
EncodingWarning,
)

with zopen(filename, "wt") as f:
f.write(content)

# Implicit text/binary `mode` warning
warnings.filterwarnings(
"ignore", category=EncodingWarning, message="argument not specified"
Expand Down
Loading