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

Drop Python 3.9 support #709

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
max-parallel: 20
matrix:
os: [ubuntu-latest, macos-14, windows-latest]
python-version: ["3.9", "3.12"]
python-version: ["3.10", "3.12"]

runs-on: ${{ matrix.os }}

Expand All @@ -30,6 +30,6 @@ jobs:
run: pytest --cov=monty --cov-report html:coverage_reports tests

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ Monty is created to serve as a complement to the Python standard library. It
provides suite of tools to solve many common problems, and hopefully,
be a resource to collect the best solutions.

Monty supports Python 3.x.
Monty supports Python 3.10+.

Please visit the [official docs](https://materialsvirtuallab.github.io/monty) for more information.
2 changes: 1 addition & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ persistent=yes

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.9
py-version=3.10

# Discover python modules and packages in the file system subtree.
recursive=no
Expand Down
39 changes: 23 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ maintainers = [
]
description = "Monty is the missing complement to Python."
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
Expand All @@ -26,11 +26,21 @@ version = "2024.7.30"

[project.optional-dependencies]
ci = [
"coverage",
"monty[optional]",
"pytest>=8",
"pytest-cov>=4",
"types-requests",
"pytest>=8",
"pytest-cov>=4",
"coverage",
"numpy<2",
"ruamel.yaml",
"msgpack",
"tqdm",
"pymongo",
"pandas",
"pint",
"pydantic",
"orjson",
"types-orjson",
"types-requests",
"torch"
DanielYang59 marked this conversation as resolved.
Show resolved Hide resolved
]
# dev is for "dev" module, not for development
dev = ["ipython"]
Expand All @@ -57,10 +67,9 @@ include = ["monty", "monty.*"]

[tool.black]
line-length = 120
target-version = ['py39']
target-version = ["py310"]
include = '\.pyi?$'
exclude = '''

(
/(
\.eggs # exclude a few common directories in the
Expand All @@ -85,26 +94,24 @@ branch = true
exclude_also = [
"@deprecated",
"def __repr__",
"if 0:",
"if __name__ == .__main__.:",
Copy link
Contributor Author

@DanielYang59 DanielYang59 Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any of these across the code base, so assume safe to remove

"if self.debug:",
"if settings.DEBUG",
"pragma: no cover",
"raise AssertionError",
"raise NotImplementedError",
"show_plot",
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:",
"except ImportError:"
]

[tool.mypy]
ignore_missing_imports = true

[tool.ruff]
lint.select = [
"I", #isort
[tool.ruff.lint]
select = [
"I", # isort
]

lint.isort.required-imports = ["from __future__ import annotations"]
lint.isort.known-first-party = ["monty"]
[tool.ruff.lint.isort]
required-imports = ["from __future__ import annotations"]
known-first-party = ["monty"]
13 changes: 13 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@
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")


Expand Down
90 changes: 45 additions & 45 deletions tests/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,57 +20,57 @@
remove,
)

test_dir = os.path.join(os.path.dirname(__file__), "test_files")
TEST_DIR = os.path.join(os.path.dirname(__file__), "test_files")


class TestCopyR:
def setup_method(self):
os.mkdir(os.path.join(test_dir, "cpr_src"))
with open(os.path.join(test_dir, "cpr_src", "test"), "w") as f:
os.mkdir(os.path.join(TEST_DIR, "cpr_src"))
with open(os.path.join(TEST_DIR, "cpr_src", "test"), "w") as f:
f.write("what")
os.mkdir(os.path.join(test_dir, "cpr_src", "sub"))
with open(os.path.join(test_dir, "cpr_src", "sub", "testr"), "w") as f:
os.mkdir(os.path.join(TEST_DIR, "cpr_src", "sub"))
with open(os.path.join(TEST_DIR, "cpr_src", "sub", "testr"), "w") as f:
f.write("what2")
if os.name != "nt":
os.symlink(
os.path.join(test_dir, "cpr_src", "test"),
os.path.join(test_dir, "cpr_src", "mysymlink"),
os.path.join(TEST_DIR, "cpr_src", "test"),
os.path.join(TEST_DIR, "cpr_src", "mysymlink"),
)

def test_recursive_copy_and_compress(self):
copy_r(os.path.join(test_dir, "cpr_src"), os.path.join(test_dir, "cpr_dst"))
assert os.path.exists(os.path.join(test_dir, "cpr_dst", "test"))
assert os.path.exists(os.path.join(test_dir, "cpr_dst", "sub", "testr"))

compress_dir(os.path.join(test_dir, "cpr_src"))
assert os.path.exists(os.path.join(test_dir, "cpr_src", "test.gz"))
assert os.path.exists(os.path.join(test_dir, "cpr_src", "sub", "testr.gz"))

decompress_dir(os.path.join(test_dir, "cpr_src"))
assert os.path.exists(os.path.join(test_dir, "cpr_src", "test"))
assert os.path.exists(os.path.join(test_dir, "cpr_src", "sub", "testr"))
with open(os.path.join(test_dir, "cpr_src", "test")) as f:
copy_r(os.path.join(TEST_DIR, "cpr_src"), os.path.join(TEST_DIR, "cpr_dst"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_dst", "test"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_dst", "sub", "testr"))

compress_dir(os.path.join(TEST_DIR, "cpr_src"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_src", "test.gz"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_src", "sub", "testr.gz"))

decompress_dir(os.path.join(TEST_DIR, "cpr_src"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_src", "test"))
assert os.path.exists(os.path.join(TEST_DIR, "cpr_src", "sub", "testr"))
with open(os.path.join(TEST_DIR, "cpr_src", "test")) as f:
txt = f.read()
assert txt == "what"

def test_pathlib(self):
test_path = Path(test_dir)
test_path = Path(TEST_DIR)
copy_r(test_path / "cpr_src", test_path / "cpr_dst")
assert (test_path / "cpr_dst" / "test").exists()
assert (test_path / "cpr_dst" / "sub" / "testr").exists()

def teardown_method(self):
shutil.rmtree(os.path.join(test_dir, "cpr_src"))
shutil.rmtree(os.path.join(test_dir, "cpr_dst"))
shutil.rmtree(os.path.join(TEST_DIR, "cpr_src"))
shutil.rmtree(os.path.join(TEST_DIR, "cpr_dst"))


class TestCompressFileDir:
def setup_method(self):
with open(os.path.join(test_dir, "tempfile"), "w") as f:
with open(os.path.join(TEST_DIR, "tempfile"), "w") as f:
f.write("hello world")

def test_compress_and_decompress_file(self):
fname = os.path.join(test_dir, "tempfile")
fname = os.path.join(TEST_DIR, "tempfile")

for fmt in ["gz", "bz2"]:
compress_file(fname, fmt)
Expand All @@ -93,8 +93,8 @@ def test_compress_and_decompress_file(self):
assert decompress_file("non-existent.bz2") is None

def test_compress_and_decompress_with_target_dir(self):
fname = os.path.join(test_dir, "tempfile")
target_dir = os.path.join(test_dir, "temp_target_dir")
fname = os.path.join(TEST_DIR, "tempfile")
target_dir = os.path.join(TEST_DIR, "temp_target_dir")

for fmt in ["gz", "bz2"]:
compress_file(fname, fmt, target_dir)
Expand All @@ -117,20 +117,20 @@ def test_compress_and_decompress_with_target_dir(self):
assert f.read() == "hello world"

def teardown_method(self):
os.remove(os.path.join(test_dir, "tempfile"))
os.remove(os.path.join(TEST_DIR, "tempfile"))


class TestGzipDir:
def setup_method(self):
os.mkdir(os.path.join(test_dir, "gzip_dir"))
with open(os.path.join(test_dir, "gzip_dir", "tempfile"), "w") as f:
os.mkdir(os.path.join(TEST_DIR, "gzip_dir"))
with open(os.path.join(TEST_DIR, "gzip_dir", "tempfile"), "w") as f:
f.write("what")

self.mtime = os.path.getmtime(os.path.join(test_dir, "gzip_dir", "tempfile"))
self.mtime = os.path.getmtime(os.path.join(TEST_DIR, "gzip_dir", "tempfile"))

def test_gzip_dir(self):
full_f = os.path.join(test_dir, "gzip_dir", "tempfile")
gzip_dir(os.path.join(test_dir, "gzip_dir"))
full_f = os.path.join(TEST_DIR, "gzip_dir", "tempfile")
gzip_dir(os.path.join(TEST_DIR, "gzip_dir"))

assert os.path.exists(f"{full_f}.gz")
assert not os.path.exists(full_f)
Expand All @@ -142,7 +142,7 @@ def test_gzip_dir(self):

def test_gzip_dir_file_coexist(self):
"""Test case where both file and file.gz exist."""
full_f = os.path.join(test_dir, "gzip_dir", "temptestfile")
full_f = os.path.join(TEST_DIR, "gzip_dir", "temptestfile")
gz_f = f"{full_f}.gz"

# Create both the file and its gzipped version
Expand All @@ -154,7 +154,7 @@ def test_gzip_dir_file_coexist(self):
with pytest.warns(
UserWarning, match="Both temptestfile and temptestfile.gz exist."
):
gzip_dir(os.path.join(test_dir, "gzip_dir"))
gzip_dir(os.path.join(TEST_DIR, "gzip_dir"))

# Verify contents of the files
with open(full_f, "r") as f:
Expand All @@ -164,13 +164,13 @@ def test_gzip_dir_file_coexist(self):
assert g.read() == b"gzipped"

def test_handle_sub_dirs(self):
sub_dir = os.path.join(test_dir, "gzip_dir", "sub_dir")
sub_dir = os.path.join(TEST_DIR, "gzip_dir", "sub_dir")
sub_file = os.path.join(sub_dir, "new_tempfile")
os.mkdir(sub_dir)
with open(sub_file, "w") as f:
f.write("anotherwhat")

gzip_dir(os.path.join(test_dir, "gzip_dir"))
gzip_dir(os.path.join(TEST_DIR, "gzip_dir"))

assert os.path.exists(f"{sub_file}.gz")
assert not os.path.exists(sub_file)
Expand All @@ -179,31 +179,31 @@ def test_handle_sub_dirs(self):
assert g.readline().decode("utf-8") == "anotherwhat"

def teardown_method(self):
shutil.rmtree(os.path.join(test_dir, "gzip_dir"))
shutil.rmtree(os.path.join(TEST_DIR, "gzip_dir"))


class TestRemove:
@unittest.skipIf(platform.system() == "Windows", "Skip on windows")
def test_remove_file(self):
tempdir = tempfile.mkdtemp(dir=test_dir)
tempdir = tempfile.mkdtemp(dir=TEST_DIR)
tempf = tempfile.mkstemp(dir=tempdir)[1]
remove(tempf)
assert not os.path.isfile(tempf)
shutil.rmtree(tempdir)

@unittest.skipIf(platform.system() == "Windows", "Skip on windows")
def test_remove_folder(self):
tempdir = tempfile.mkdtemp(dir=test_dir)
tempdir = tempfile.mkdtemp(dir=TEST_DIR)
remove(tempdir)
assert not os.path.isdir(tempdir)

@unittest.skipIf(platform.system() == "Windows", "Skip on windows")
def test_remove_symlink(self):
tempdir = tempfile.mkdtemp(dir=test_dir)
tempdir = tempfile.mkdtemp(dir=TEST_DIR)
tempf = tempfile.mkstemp(dir=tempdir)[1]

os.symlink(tempdir, os.path.join(test_dir, "temp_link"))
templink = os.path.join(test_dir, "temp_link")
os.symlink(tempdir, os.path.join(TEST_DIR, "temp_link"))
templink = os.path.join(TEST_DIR, "temp_link")
remove(templink)
assert os.path.isfile(tempf)
assert os.path.isdir(tempdir)
Expand All @@ -212,11 +212,11 @@ def test_remove_symlink(self):

@unittest.skipIf(platform.system() == "Windows", "Skip on windows")
def test_remove_symlink_follow(self):
tempdir = tempfile.mkdtemp(dir=test_dir)
tempdir = tempfile.mkdtemp(dir=TEST_DIR)
tempf = tempfile.mkstemp(dir=tempdir)[1]

os.symlink(tempdir, os.path.join(test_dir, "temp_link"))
templink = os.path.join(test_dir, "temp_link")
os.symlink(tempdir, os.path.join(TEST_DIR, "temp_link"))
templink = os.path.join(TEST_DIR, "temp_link")
remove(templink, follow_symlink=True)
assert not os.path.isfile(tempf)
assert not os.path.isdir(tempdir)
Expand Down
Loading