Files
panaetius/duties.py
dtomlinson91 03be9558f8 chore: release 2.3.3 (#2)
* linting

* adding py.typed

* adding pyyaml types

* workaround #1

* updating latest

* updating dev dependencies

* adding isort mypy safety

* chore: prepare release 2.3.3
2021-11-20 22:09:08 +00:00

322 lines
7.8 KiB
Python

from __future__ import annotations
import importlib
import os
import pathlib
import re
import shutil
import sys
from io import StringIO
from duty import duty
PACKAGE_NAME = "panaetius"
@duty
def update_deps(ctx, dry: bool = False):
"""
Update the dependencies using Poetry.
Args:
ctx: The context instance (passed automatically).
dry (bool, optional) = If True will update the `poetry.lock` without updating the
dependencies themselves. Defaults to False.
Example:
`duty update_deps dry=False`
"""
dry_run = "--dry-run" if dry else ""
ctx.run(
["poetry", "update", dry_run],
title=f"Updating poetry deps {dry_run}",
)
@duty
def test(ctx):
"""
Run tests using pytest.
Args:
ctx: The context instance (passed automatically).
"""
pytest_results = ctx.run(["pytest", "-v"], pty=True)
print(pytest_results)
@duty
def coverage(ctx):
"""
Generate a coverage report and save to XML and HTML.
Args:
ctx: The context instance (passed automatically).
Example:
`duty coverage`
"""
ctx.run(["coverage", "run", "--source", PACKAGE_NAME, "-m", "pytest"])
res = ctx.run(["coverage", "report"], pty=True)
print(res)
ctx.run(["coverage", "html"])
ctx.run(["coverage", "xml"])
@duty
def version(ctx, bump: str = "patch"):
"""
Bump the version using Poetry and update _version.py.
Args:
ctx: The context instance (passed automatically).
bump (str, optional) = poetry version flag. Available options are:
patch, minor, major, prepatch, preminor, premajor, prerelease.
Defaults to patch.
Example:
`duty version bump=major`
"""
# bump with poetry
result = ctx.run(["poetry", "version", bump])
new_version = re.search(r"(?:.*)(?:\s)(\d+\.\d+\.\d+)$", result)
print(new_version.group(0))
# update _version.py
version_file = pathlib.Path(PACKAGE_NAME) / "_version.py"
with version_file.open("w", encoding="utf-8") as version_file:
version_file.write(
f'"""Module containing the version of {PACKAGE_NAME}."""\n\n' + f'__version__ = "{new_version.group(1)}"\n'
)
print(f"Bumped _version.py to {new_version.group(1)}")
@duty
def build(ctx):
"""
Build with poetry and extract the setup.py and copy to project root.
Args:
ctx: The context instance (passed automatically).
Example:
`duty build`
"""
repo_root = pathlib.Path(".")
# build with poetry
result = ctx.run(["poetry", "build"])
print(result)
# extract the setup.py from the tar
extracted_tar = re.search(r"(?:.*)(?:Built\s)(.*)", result)
tar_file = pathlib.Path(f"./dist/{extracted_tar.group(1)}")
shutil.unpack_archive(tar_file, tar_file.parents[0])
# copy setup.py to repo root
extracted_path = tar_file.parents[0] / os.path.splitext(tar_file.stem)[0]
setup_py = extracted_path / "setup.py"
shutil.copyfile(setup_py, (repo_root / "setup.py"))
# cleanup
shutil.rmtree(extracted_path)
@duty
def export(ctx):
"""
Export the dependencies to a requirements.txt file.
Args:
ctx: The context instance (passed automatically).
Example:
`duty export`
"""
requirements_content = ctx.run(
[
"poetry",
"export",
"-f",
"requirements.txt",
"--without-hashes",
]
)
requirements_dev_content = ctx.run(
[
"poetry",
"export",
"-f",
"requirements.txt",
"--without-hashes",
"--dev",
]
)
requirements = pathlib.Path(".") / "requirements.txt"
requirements_dev = pathlib.Path(".") / "requirements_dev.txt"
with requirements.open("w", encoding="utf-8") as req:
req.write(requirements_content)
with requirements_dev.open("w", encoding="utf-8") as req:
req.write(requirements_dev_content)
@duty
def publish(ctx, password: str):
"""
Publish the package to pypi.org.
Args:
ctx: The context instance (passed automatically).
password (str): pypi.org password.
Example:
`duty publish password=$my_password`
"""
dist_dir = pathlib.Path(".") / "dist"
rm_result = rm_tree(dist_dir)
print(rm_result)
publish_result = ctx.run(["poetry", "publish", "-u", "dtomlinson", "-p", password, "--build"])
print(publish_result)
@duty(silent=True)
def clean(ctx):
"""
Delete temporary files.
Args:
ctx: The context instance (passed automatically).
"""
ctx.run("rm -rf .mypy_cache")
ctx.run("rm -rf .pytest_cache")
ctx.run("rm -rf tests/.pytest_cache")
ctx.run("rm -rf build")
ctx.run("rm -rf dist")
ctx.run("rm -rf pip-wheel-metadata")
ctx.run("rm -rf site")
ctx.run("rm -rf coverage.xml")
ctx.run("rm -rf pytest.xml")
ctx.run("rm -rf htmlcov")
ctx.run("find . -iname '.coverage*' -not -name .coveragerc | xargs rm -rf")
ctx.run("find . -type d -name __pycache__ | xargs rm -rf")
ctx.run("find . -name '*.rej' -delete")
@duty
def format(ctx):
"""
Format code using Black and isort.
Args:
ctx: The context instance (passed automatically).
"""
res = ctx.run(["black", "--line-length=99", PACKAGE_NAME], pty=True, title="Running Black")
print(res)
res = ctx.run(["isort", PACKAGE_NAME])
print(res)
@duty(pre=["check_code_quality", "check_types", "check_docs", "check_dependencies"])
def check(ctx):
"""
Check the code quality, check types, check documentation builds and check dependencies for vulnerabilities.
Args:
ctx: The context instance (passed automatically).
"""
@duty
def check_code_quality(ctx):
"""
Check the code quality using prospector.
Args:
ctx: The context instance (passed automatically).
"""
ctx.run(["prospector", PACKAGE_NAME], pty=True, title="Checking code quality with prospector")
@duty
def check_types(ctx):
"""
Check the types using mypy.
Args:
ctx: The context instance (passed automatically).
"""
ctx.run(["mypy", PACKAGE_NAME], pty=True, title="Checking types with MyPy")
@duty
def check_docs(ctx):
"""
Check the documentation builds successfully.
Args:
ctx: The context instance (passed automatically).
"""
ctx.run(["mkdocs", "build"], title="Building documentation")
@duty
def check_dependencies(ctx):
"""
Check dependencies with safety for vulnerabilities.
Args:
ctx: The context instance (passed automatically).
"""
for module in sys.modules:
if module.startswith("safety.") or module == "safety":
del sys.modules[module]
importlib.invalidate_caches()
from safety import safety
from safety.formatter import report
from safety.util import read_requirements
requirements = ctx.run(
"poetry export --dev --without-hashes",
title="Exporting dependencies as requirements",
allow_overrides=False,
)
def check_vulns():
packages = list(read_requirements(StringIO(requirements)))
vulns = safety.check(packages=packages, ignore_ids="41002", key="", db_mirror="", cached=False, proxy={})
output_report = report(vulns=vulns, full=True, checked_packages=len(packages))
print(vulns)
if vulns:
print(output_report)
ctx.run(
check_vulns,
stdin=requirements,
title="Checking dependencies",
pty=True,
)
def rm_tree(directory: pathlib.Path):
"""
Recursively delete a directory and all its contents.
Args:
directory (pathlib.Path): The directory to delete.
"""
for child in directory.glob("*"):
if child.is_file():
child.unlink()
else:
rm_tree(child)
directory.rmdir()