diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..fdfc7cc --- /dev/null +++ b/.flake8 @@ -0,0 +1,29 @@ +[flake8] +exclude = + .git + __pycache__ + build + dist + examples + autotest +ignore = + # https://flake8.pycqa.org/en/latest/user/error-codes.html + F401, + # https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes + # Indentation + E121, E122, E126, E127, E128, + # Whitespace + E203, E221, E222, E226, E231, E241, + # Import + E402, + # Line length + E501, E502, + # Statement + E722, E741, + # Whitespace warning + W291, W292, W293, + # Blank line warning + W391, + # Line break warning + W503, W504 +statistics = True \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcbac55..384cf20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: with: python-version: 3.8 cache: 'pip' - cache-dependency-path: setup.cfg + cache-dependency-path: pyproject.toml - name: Install Python packages run: | @@ -132,7 +132,7 @@ jobs: python-version: ${{ matrix.python }} cache: 'pip' cache-dependency-path: | - modflow-devtools/setup.cfg + modflow-devtools/pyproject.toml modflow6-examples/etc/requirements*.txt - name: Install Python packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65e1a31..1ef838e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: with: python-version: 3.8 cache: 'pip' - cache-dependency-path: setup.cfg + cache-dependency-path: pyproject.toml - name: Install Python dependencies run: | @@ -214,7 +214,7 @@ jobs: with: python-version: 3.8 cache: 'pip' - cache-dependency-path: setup.cfg + cache-dependency-path: pyproject.toml - name: Install Python dependencies run: | diff --git a/HISTORY.md b/HISTORY.md index 2812fdc..6722d37 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,9 @@ +### Version 0.1.5 + +#### Refactoring + +* [refactor(metadata)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/2edeacfd8cb10081c22d1ab0799aba1fa7522c0d): Use pyproject.toml, retire setup.cfg (#63). Committed by w-bonelli on 2023-01-19. + ### Version 0.1.4 #### Bug fixes diff --git a/MANIFEST.in b/MANIFEST.in index bdeb112..6851bc0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ global-exclude .DS_Store *.pyc *.pyo *.pyd *.swp *.bak *~ .* *.sh *.yml *.md *.toml exclude autotest/* include pyproject.toml -include version.txt \ No newline at end of file +include version.txt +include README.md \ No newline at end of file diff --git a/README.md b/README.md index 1957ca3..966093e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MODFLOW developer tools -### Version 0.1.4 — release candidate +### Version 0.1.5 — release candidate [![GitHub tag](https://img.shields.io/github/tag/MODFLOW-USGS/modflow-devtools.svg)](https://github.com/MODFLOW-USGS/modflow-devtools/tags/latest) [![PyPI Version](https://img.shields.io/pypi/v/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools) [![PyPI Versions](https://img.shields.io/pypi/pyversions/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools) diff --git a/docs/conf.py b/docs/conf.py index e3cddd3..6269fa1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ project = "modflow-devtools" author = "MODFLOW Team" -release = "0.1.4" +release = "0.1.5" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/md/zip.md b/docs/md/zip.md index f362fe1..f4fd6ab 100644 --- a/docs/md/zip.md +++ b/docs/md/zip.md @@ -1,3 +1,34 @@ # `MFZipFile` -Python's [`ZipFile`](https://docs.python.org/3/library/zipfile.html) doesn't [preserve file permissions at extraction time](https://bugs.python.org/issue15795). The `MFZipFile` subclass modifies `ZipFile.extract()` to do so, as per the recommendation [here](https://stackoverflow.com/questions/39296101/python-zipfile-removes-execute-permissions-from-binaries), and maintains identical behavior otherwise. \ No newline at end of file +Python's [`ZipFile`](https://docs.python.org/3/library/zipfile.html) doesn't [preserve file permissions at extraction time](https://bugs.python.org/issue15795). The `MFZipFile` subclass: + +- modifies `ZipFile.extract()` to preserve permissions per the [recommendation here](https://stackoverflow.com/questions/39296101/python-zipfile-removes-execute-permissions-from-binaries) +- adds a static `ZipFile.compressall()` method to create a zip file from files and directories +- maintains an otherwise identical API + +## `compressall` + +The `compressall` method is a static method that creates a zip file from lists of files and/or directories. It is a convenience method that wraps `ZipFile.write()`, `ZipFile.close()`, etc. + +```python +from zipfile import ZipFile +from modflow_devtools.zip import MFZipFile + +def test_compressall(function_tmpdir): + zip_file = function_tmpdir / "output.zip" + + input_dir = function_tmpdir / "input" + input_dir.mkdir() + + with open(input_dir / "data.txt", "w") as f: + f.write("hello world") + + MFZipFile.compressall(str(zip_file), dir_pths=str(input_dir)) + assert zip_file.exists() + + output_dir = function_tmpdir / "output" + output_dir.mkdir() + + ZipFile(zip_file).extractall(path=str(output_dir)) + assert (output_dir / "data.txt").is_file() +``` \ No newline at end of file diff --git a/modflow_devtools/__init__.py b/modflow_devtools/__init__.py index 29535cf..54167e8 100644 --- a/modflow_devtools/__init__.py +++ b/modflow_devtools/__init__.py @@ -1,6 +1,6 @@ __author__ = "Joseph D. Hughes" -__date__ = "Jan 18, 2023" -__version__ = "0.1.4" +__date__ = "Jan 19, 2023" +__version__ = "0.1.5" __maintainer__ = "Joseph D. Hughes" __email__ = "jdhughes@usgs.gov" __status__ = "Production" diff --git a/modflow_devtools/test/test_zip.py b/modflow_devtools/test/test_zip.py new file mode 100644 index 0000000..b1aa28a --- /dev/null +++ b/modflow_devtools/test/test_zip.py @@ -0,0 +1,92 @@ +import os +import shutil +import sys +import zipfile +from os import environ +from pathlib import Path +from pprint import pprint +from zipfile import ZipFile + +import pytest +from modflow_devtools.markers import excludes_platform +from modflow_devtools.misc import get_suffixes, set_dir +from modflow_devtools.zip import MFZipFile + +_bin_path = Path(environ.get("BIN_PATH")).expanduser().absolute() +_ext, _ = get_suffixes(sys.platform) + + +@pytest.fixture(scope="module") +def empty_archive(module_tmpdir) -> Path: + # https://stackoverflow.com/a/25195628/6514033 + data = b"PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + path = module_tmpdir / "empty.zip" + + with open(path, "wb") as zip: + zip.write(data) + + return path + + +@pytest.fixture(scope="module") +def nonempty_archive(module_tmpdir) -> Path: + if not _bin_path.is_dir(): + pytest.skip(f"BIN_PATH ({_bin_path}) is not a directory") + + zip_path = module_tmpdir / "nonempty.zip" + txt_path = module_tmpdir / "hw.txt" + exe_path = _bin_path / f"mf6{_ext}" + + # create a zip file with a text file and an executable + shutil.copy(exe_path, module_tmpdir) + with open(txt_path, "w") as f: + f.write("hello world") + + with set_dir(module_tmpdir): + zip = MFZipFile(zip_path.name, "w") + zip.write(txt_path.name, compress_type=zipfile.ZIP_DEFLATED) + zip.write(exe_path.name, compress_type=zipfile.ZIP_DEFLATED) + zip.close() + + return zip_path + + +def test_compressall(function_tmpdir): + zip_file = function_tmpdir / "output.zip" + input_dir = function_tmpdir / "input" + input_dir.mkdir() + + with open(input_dir / "data.txt", "w") as f: + f.write("hello world") + + MFZipFile.compressall(str(zip_file), dir_pths=str(input_dir)) + + pprint(list(function_tmpdir.iterdir())) + assert zip_file.exists() + + output_dir = function_tmpdir / "output" + output_dir.mkdir() + + ZipFile(zip_file).extractall(path=str(output_dir)) + + pprint(list(output_dir.iterdir())) + assert (output_dir / "data.txt").is_file() + + +def test_extractall_empty(empty_archive, function_tmpdir): + zf = MFZipFile(empty_archive, "r") + zf.extractall(str(function_tmpdir)) + + assert not any(function_tmpdir.iterdir()) + + +@pytest.mark.parametrize("mf", [True, False]) +@excludes_platform("Windows") +def test_preserves_execute_permission(function_tmpdir, nonempty_archive, mf): + zip = MFZipFile(nonempty_archive) if mf else ZipFile(nonempty_archive) + zip.extractall(path=str(function_tmpdir)) + + exe_path = function_tmpdir / f"mf6{_ext}" + + assert exe_path.is_file() + assert os.access(exe_path, os.X_OK) == mf diff --git a/pyproject.toml b/pyproject.toml index a9a9ec6..46d8cfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,80 @@ [build-system] # Minimum requirements for the build system to execute requires = [ - "setuptools>=45", - "wheel", + "setuptools>=61", ] build-backend = "setuptools.build_meta" +[project] +name = "modflow-devtools" +description = "Python tools for MODFLOW development" +authors = [ + {name = "Joseph D. Hughes", email = "modflow@usgs.gov"}, +] +maintainers = [ + {name = "Joseph D. Hughes", email = "modflow@usgs.gov"}, +] +keywords = [ + "MODFLOW", + "development", + "utilities", + "groundwater", + "hydrogeology" +] +readme = "README.md" +license = {text = "CC0"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Hydrology" +] +requires-python = ">=3.8" +dependencies = [ + "numpy", + "pytest" +] +dynamic = ["version"] + +[project.optional-dependencies] +lint = [ + "black", + "cffconvert", + "flake8", + "isort", + "pylint" +] +test = [ + "modflow-devtools[lint]", + "coverage", + "flaky", + "filelock", + "meson!=0.63.0", + "ninja", + "pytest-cases", + "pytest-cov", + "pytest-dotenv", + "pytest-xdist", + "PyYaml" +] +docs = [ + "sphinx", + "sphinx-rtd-theme", + "myst-parser" +] + +[project.urls] +"Documentation" = "https://modflow-devtools.readthedocs.io/en/latest/" +"Bug Tracker" = "https://github.com/MODFLOW-USGS/modflow-devtools/issues" +"Source Code" = "https://github.com/MODFLOW-USGS/modflow-devtools" + + [tool.black] line-length = 79 target_version = ["py37"] @@ -19,6 +88,13 @@ profile = "black" src_paths = ["src/modflow_devtools"] line_length = 79 +[tool.setuptools] +include-package-data = true +zip-safe = false + +[tool.setuptools.dynamic] +version = {file = "version.txt"} + [tool.setuptools_scm] fallback_version = "999" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 172edeb..0000000 --- a/setup.cfg +++ /dev/null @@ -1,99 +0,0 @@ -[metadata] -name = modflow-devtools -version = file: version.txt -description = modflow-devtools is a Python package containing tools for MODFLOW development. -long_description = file: README.md -long_description_content_type = text/markdown -author = Joseph D. Hughes -author_email = modflow@usgs.gov -maintainer = Joseph D. Hughes -maintainer_email = jdhughes@usgs.gov -license = CC0 -license_files = LICENSE.md -platform = Windows, Mac OS-X, Linux -keywords = MODFLOW, development -classifiers = - Development Status :: 3 - Alpha - Intended Audience :: Science/Research - License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication - Operating System :: Microsoft :: Windows - Operating System :: POSIX - Operating System :: Unix - Operating System :: MacOS - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3 :: Only - Topic :: Scientific/Engineering :: Hydrology -url = https://github.com/MODFLOW-USGS/modflow-devtools -download_url = https://pypi.org/project/modflow-devtools -project_urls = - Documentation = https://mfpymake.readthedocs.io - Bug Tracker = https://github.com/MODFLOW-USGS/modflow-devtools/issues - Source Code = https://github.com/MODFLOW-USGS/modflow-devtools - -[options] -include_package_data = True -zip_safe = False -packages = find: -python_requires = >=3.8 -install_requires = - numpy - pytest - -[options.extras_require] -lint = - black - cffconvert - flake8 - isort - pylint -test = - %(lint)s - coverage - flaky - filelock - meson!=0.63.0 - ninja - pytest-cases - pytest-cov - pytest-dotenv - pytest-xdist - PyYaml -docs = - sphinx - sphinx-rtd-theme - myst-parser - - -[flake8] -exclude = - .git - __pycache__ - build - dist - autotest -ignore = - # https://flake8.pycqa.org/en/latest/user/error-codes.html - F401, - # https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes - # Indentation - E121, E122, E126, E127, E128, - # Whitespace - E203, E221, E222, E226, E231, E241, - # Import - E402, - # Line length - E501, E502, - # Statement - E722, E741, - # Whitespace warning - W291, W292, W293, - # Blank line warning - W391, - # Line break warning - W503, W504 -statistics = True diff --git a/setup.py b/setup.py index 6068493..e4505ea 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,3 @@ from setuptools import setup -setup() +setup(name="modflow-devtools") diff --git a/version.txt b/version.txt index 446ba66..def9a01 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.1.4 \ No newline at end of file +0.1.5 \ No newline at end of file