diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24e816d9a..0a70457bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,6 +5,7 @@ on: [push, pull_request, workflow_call] jobs: build: strategy: + fail-fast: false max-parallel: 20 matrix: os: [ubuntu-latest, macos-14, windows-latest] diff --git a/pyproject.toml b/pyproject.toml index b0b8683ab..7c10b37b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,31 +19,37 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - + "ruamel.yaml", + "numpy<2.0.0", ] version = "2024.7.30" [project.optional-dependencies] ci = [ - "pytest>=8", - "pytest-cov>=4", - "coverage", - "numpy<2.0.0", - "ruamel.yaml", - "msgpack", - "tqdm", - "pymongo", - "pandas", - "pint", - "orjson", - "types-orjson", - "types-requests", - "torch" + "coverage", + "monty[optional]", + "pytest>=8", + "pytest-cov>=4", + "types-requests", ] +# dev is for "dev" module, not for development +dev = ["ipython"] docs = [ "sphinx", "sphinx_rtd_theme", ] +json = [ + "bson", + "orjson>=3.6.1", + "pandas", + "pydantic", + "pint", + "torch", +] +multiprocessing = ["tqdm"] +optional = ["monty[dev,json,multiprocessing,serialization]"] +serialization = ["msgpack"] +task = ["requests", "invoke"] [tool.setuptools.packages.find] where = ["src"] @@ -101,3 +107,4 @@ lint.select = [ ] lint.isort.required-imports = ["from __future__ import annotations"] +lint.isort.known-first-party = ["monty"] diff --git a/src/monty/dev.py b/src/monty/dev.py index 353674e40..cf468bce4 100644 --- a/src/monty/dev.py +++ b/src/monty/dev.py @@ -231,9 +231,8 @@ def install_excepthook(hook_type: str = "color", **kwargs) -> int: """ try: from IPython.core import ultratb # pylint: disable=import-outside-toplevel - except ImportError: - warnings.warn("Cannot install excepthook, IPyhon.core.ultratb not available") - return 1 + except ImportError as exc: + raise ImportError("Cannot install excepthook, IPython not installed") from exc # Select the hook. hook = dict( diff --git a/src/monty/io.py b/src/monty/io.py index f24a63ddd..9ca0f2cb9 100644 --- a/src/monty/io.py +++ b/src/monty/io.py @@ -9,11 +9,7 @@ import errno import gzip import io - -try: - import lzma -except ImportError: - lzma = None # type: ignore[assignment] +import lzma import mmap import os import subprocess @@ -49,7 +45,7 @@ def zopen(filename: Union[str, Path], *args, **kwargs) -> IO: return bz2.open(filename, *args, **kwargs) if ext in {".GZ", ".Z"}: return gzip.open(filename, *args, **kwargs) - if lzma is not None and ext in {".XZ", ".LZMA"}: + if ext in {".XZ", ".LZMA"}: return lzma.open(filename, *args, **kwargs) return open(filename, *args, **kwargs) diff --git a/src/monty/itertools.py b/src/monty/itertools.py index 5a112a03d..c4ed76d5c 100644 --- a/src/monty/itertools.py +++ b/src/monty/itertools.py @@ -7,10 +7,7 @@ import itertools from typing import TYPE_CHECKING -try: - import numpy as np -except ImportError: - np = None +import numpy as np if TYPE_CHECKING: from typing import Iterable diff --git a/src/monty/json.py b/src/monty/json.py index f98990974..67c3e0eda 100644 --- a/src/monty/json.py +++ b/src/monty/json.py @@ -21,10 +21,8 @@ from typing import Any from uuid import UUID, uuid4 -try: - import numpy as np -except ImportError: - np = None +import numpy as np +from ruamel.yaml import YAML try: import pydantic @@ -41,17 +39,11 @@ except ImportError: bson = None -try: - from ruamel.yaml import YAML -except ImportError: - YAML = None - try: import orjson except ImportError: orjson = None - try: import torch except ImportError: @@ -599,23 +591,22 @@ def default(self, o) -> dict: d["data"] = o.numpy().tolist() return d - if np is not None: - if isinstance(o, np.ndarray): - if str(o.dtype).startswith("complex"): - return { - "@module": "numpy", - "@class": "array", - "dtype": str(o.dtype), - "data": [o.real.tolist(), o.imag.tolist()], - } + if isinstance(o, np.ndarray): + if str(o.dtype).startswith("complex"): return { "@module": "numpy", "@class": "array", "dtype": str(o.dtype), - "data": o.tolist(), + "data": [o.real.tolist(), o.imag.tolist()], } - if isinstance(o, np.generic): - return o.item() + return { + "@module": "numpy", + "@class": "array", + "dtype": str(o.dtype), + "data": o.tolist(), + } + if isinstance(o, np.generic): + return o.item() if _check_type(o, "pandas.core.frame.DataFrame"): return { @@ -668,7 +659,7 @@ def default(self, o) -> dict: and dataclasses.is_dataclass(o) ): # This handles dataclasses that are not subclasses of MSONAble. - d = dataclasses.asdict(o) # type: ignore[call-overload] + d = dataclasses.asdict(o) # type: ignore[call-overload, arg-type] elif hasattr(o, "as_dict"): d = o.as_dict() elif isinstance(o, Enum): @@ -813,7 +804,7 @@ def process_decoded(self, d): ).type(d["dtype"]) return torch.tensor(d["data"]).type(d["dtype"]) # pylint: disable=E1101 - elif np is not None and modname == "numpy" and classname == "array": + elif modname == "numpy" and classname == "array": if d["dtype"].startswith("complex"): return np.array( [ @@ -936,7 +927,8 @@ def jsanitize( ) for i in obj ] - if np is not None and isinstance(obj, np.ndarray): + + if isinstance(obj, np.ndarray): try: return [ jsanitize( @@ -950,8 +942,10 @@ def jsanitize( ] except TypeError: return obj.tolist() - if np is not None and isinstance(obj, np.generic): + + if isinstance(obj, np.generic): return obj.item() + if _check_type( obj, ( diff --git a/src/monty/multiprocessing.py b/src/monty/multiprocessing.py index 25511820c..4b7c720f2 100644 --- a/src/monty/multiprocessing.py +++ b/src/monty/multiprocessing.py @@ -9,8 +9,8 @@ try: from tqdm.autonotebook import tqdm -except ImportError: - tqdm = None +except ImportError as exc: + raise ImportError("tqdm must be installed for this function.") from exc def imap_tqdm(nprocs: int, func: Callable, iterable: Iterable, *args, **kwargs) -> list: @@ -28,8 +28,6 @@ def imap_tqdm(nprocs: int, func: Callable, iterable: Iterable, *args, **kwargs) Returns: Results of Pool.imap. """ - if tqdm is None: - raise ImportError("tqdm must be installed for this function.") data = [] with Pool(nprocs) as pool: try: diff --git a/src/monty/serialization.py b/src/monty/serialization.py index a17b99131..6732a674a 100644 --- a/src/monty/serialization.py +++ b/src/monty/serialization.py @@ -9,10 +9,7 @@ import os from typing import TYPE_CHECKING -try: - from ruamel.yaml import YAML -except ImportError: - YAML = None # type: ignore[arg-type] +from ruamel.yaml import YAML from monty.io import zopen from monty.json import MontyDecoder, MontyEncoder diff --git a/tasks.py b/tasks.py index 921412adb..7432034a0 100755 --- a/tasks.py +++ b/tasks.py @@ -11,6 +11,7 @@ import requests from invoke import task + from monty import __version__ as ver from monty.os import cd diff --git a/tests/test_collections.py b/tests/test_collections.py index 8057f7bc2..58d2af9e7 100644 --- a/tests/test_collections.py +++ b/tests/test_collections.py @@ -3,6 +3,7 @@ import os import pytest + from monty.collections import AttrDict, FrozenAttrDict, Namespace, frozendict, tree TEST_DIR = os.path.join(os.path.dirname(__file__), "test_files") diff --git a/tests/test_design_patterns.py b/tests/test_design_patterns.py index c37bc87da..67a7ffc2b 100644 --- a/tests/test_design_patterns.py +++ b/tests/test_design_patterns.py @@ -6,6 +6,7 @@ from typing import Any import pytest + from monty.design_patterns import cached_class, singleton diff --git a/tests/test_dev.py b/tests/test_dev.py index 8c44d8aec..13bb211be 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -7,6 +7,7 @@ from unittest.mock import patch import pytest + from monty.dev import deprecated, install_excepthook, requires # Set all warnings to always be triggered. diff --git a/tests/test_fractions.py b/tests/test_fractions.py index 7d5f27709..230dcf5a7 100644 --- a/tests/test_fractions.py +++ b/tests/test_fractions.py @@ -1,6 +1,7 @@ from __future__ import annotations import pytest + from monty.fractions import gcd, gcd_float, lcm diff --git a/tests/test_functools.py b/tests/test_functools.py index 8bf3fabff..120e6bd75 100644 --- a/tests/test_functools.py +++ b/tests/test_functools.py @@ -5,6 +5,7 @@ import unittest import pytest + from monty.functools import ( TimeoutError, lazy_property, diff --git a/tests/test_io.py b/tests/test_io.py index 9daa17bef..052bc471b 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -5,6 +5,7 @@ from unittest.mock import patch import pytest + from monty.io import ( FileLock, FileLockException, diff --git a/tests/test_json.py b/tests/test_json.py index 9c9e572c7..e59e89d57 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -8,10 +8,19 @@ from enum import Enum from typing import Union -try: - import numpy as np -except ImportError: - np = None +import numpy as np +import pytest + +from monty.json import ( + MontyDecoder, + MontyEncoder, + MSONable, + _load_redirect, + jsanitize, + load, +) + +from . import __version__ as TESTS_VERSION try: import pandas as pd @@ -38,18 +47,6 @@ except ImportError: ObjectId = None -import pytest -from monty.json import ( - MontyDecoder, - MontyEncoder, - MSONable, - _load_redirect, - jsanitize, - load, -) - -from . import __version__ as tests_version - TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files") @@ -320,7 +317,7 @@ def test_unsafe_hash(self): def test_version(self): obj = self.good_cls("Hello", "World", "Python") d = obj.as_dict() - assert d["@version"] == tests_version + assert d["@version"] == TESTS_VERSION def test_nested_to_from_dict(self): GMC = GoodMSONClass @@ -564,7 +561,6 @@ def test_nan(self): d = json.loads(djson) assert isinstance(d[0], float) - @pytest.mark.skipif(np is None, reason="numpy not present") def test_numpy(self): x = np.array([1, 2, 3], dtype="int64") with pytest.raises(TypeError): @@ -872,9 +868,7 @@ def test_jsanitize_pandas(self): clean = jsanitize(s) assert clean == s.to_dict() - @pytest.mark.skipif( - np is None or ObjectId is None, reason="numpy and bson not present" - ) + @pytest.mark.skipif(ObjectId is None, reason="bson not present") def test_jsanitize_numpy_bson(self): d = { "a": ["b", np.array([1, 2, 3])], diff --git a/tests/test_os.py b/tests/test_os.py index c6ca8d7ca..af5b156f4 100644 --- a/tests/test_os.py +++ b/tests/test_os.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest + from monty.os import cd, makedirs_p from monty.os.path import find_exts, zpath diff --git a/tests/test_serialization.py b/tests/test_serialization.py index d2e0cf9cb..e7c853a58 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -7,14 +7,14 @@ import pytest +from monty.serialization import dumpfn, loadfn +from monty.tempfile import ScratchDir + try: import msgpack except ImportError: msgpack = None -from monty.serialization import dumpfn, loadfn -from monty.tempfile import ScratchDir - class TestSerial: @classmethod diff --git a/tests/test_shutil.py b/tests/test_shutil.py index 16cd9aa78..e506b4ae4 100644 --- a/tests/test_shutil.py +++ b/tests/test_shutil.py @@ -9,6 +9,7 @@ from pathlib import Path import pytest + from monty.shutil import ( compress_dir, compress_file, diff --git a/tests/test_tempfile.py b/tests/test_tempfile.py index 4dfd3b17f..088aec670 100644 --- a/tests/test_tempfile.py +++ b/tests/test_tempfile.py @@ -4,6 +4,7 @@ import shutil import pytest + from monty.tempfile import ScratchDir TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files")