mirror of
https://github.com/tembo-pages/tembo-core.git
synced 2025-12-22 13:35:43 +00:00
chore: add cliff.toml Using git-cliff to auto generate changelogs. chore: update cliff.toml chore: updating changelog chore: remove old changelog chore: update cliff.toml chore: add changelog duty using git-cliff
359 lines
8.9 KiB
Python
359 lines
8.9 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from io import StringIO
|
|
from typing import List, Optional, Pattern
|
|
from urllib.request import urlopen
|
|
|
|
from duty import duty
|
|
|
|
PACKAGE_NAME = "tembo"
|
|
|
|
|
|
@duty(post=["export"])
|
|
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 bump(ctx, version: str = "patch"):
|
|
"""
|
|
Bump the version using Poetry and update _version.py.
|
|
|
|
Args:
|
|
ctx: The context instance (passed automatically).
|
|
version (str, optional) = poetry version flag. Available options are:
|
|
patch, minor, major. Defaults to patch.
|
|
|
|
Example:
|
|
`duty bump version=major`
|
|
"""
|
|
|
|
# bump with poetry
|
|
result = ctx.run(["poetry", "version", version])
|
|
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 release(ctx, version: str = "patch") -> None:
|
|
"""
|
|
Prepare package for a new release.
|
|
|
|
Will run bump, build, export. Manual running of publish is required afterwards.
|
|
|
|
Args:
|
|
ctx: The context instance (passed automatically).
|
|
version (str): poetry version flag. Available options are: patch, minor, major.
|
|
"""
|
|
print(ctx.run(["duty", "bump", f"version={version}"]))
|
|
ctx.run(["duty", "build"])
|
|
ctx.run(["duty", "export"])
|
|
|
|
|
|
@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,
|
|
)
|
|
|
|
@duty
|
|
def changelog(ctx, planned_release: Optional[str] = None):
|
|
"""
|
|
Generate a changelog with git-cliff.
|
|
|
|
Args:
|
|
ctx: The context instance (passed automatically).
|
|
planned_release (str, optional): The planned release version. Example: v1.0.2
|
|
"""
|
|
if planned_release is not None:
|
|
changelog = ctx.run(["git", "cliff", "--tag", planned_release])
|
|
else:
|
|
changelog = ctx.run(["git", "cliff"])
|
|
|
|
changelog_file = pathlib.Path(".") / "CHANGELOG.md"
|
|
with changelog_file.open("w", encoding="utf-8") as changelog_contents:
|
|
changelog_contents.write(changelog)
|
|
|
|
|
|
|
|
|
|
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()
|