hassle.hassle_utilities

  1import os
  2
  3import black
  4import packagelister
  5import vermin
  6from gitbetter import git
  7from pathier import Pathier
  8
  9from hassle import hassle_config
 10
 11root = Pathier(__file__).parent
 12
 13
 14def increment_version(pyproject_path: Pathier, increment_type: str):
 15    """Increment the project.version field in pyproject.toml.
 16
 17    :param package_path: Path to the package/project directory.
 18
 19    :param increment_type: One from 'major', 'minor', or 'patch'."""
 20    meta = pyproject_path.loads()
 21    major, minor, patch = [int(num) for num in meta["project"]["version"].split(".")]
 22    if increment_type == "major":
 23        major += 1
 24        minor = 0
 25        patch = 0
 26    elif increment_type == "minor":
 27        minor += 1
 28        patch = 0
 29    elif increment_type == "patch":
 30        patch += 1
 31    incremented_version = ".".join(str(num) for num in [major, minor, patch])
 32    meta["project"]["version"] = incremented_version
 33    pyproject_path.dumps(meta)
 34
 35
 36def get_minimum_py_version(src: str) -> str:
 37    """Scan src with vermin and return minimum
 38    python version."""
 39    config = vermin.Config()
 40    config.add_backport("typing")
 41    config.add_backport("typing_extensions")
 42    config.set_eval_annotations(True)
 43    result = vermin.visit(src, config).minimum_versions()[1]
 44    return f"{result[0]}.{result[1]}"
 45
 46
 47def get_project_code(project_path: Pathier) -> str:
 48    """Read and return all code from project_path
 49    as one string."""
 50    return "\n".join(file.read_text() for file in project_path.rglob("*.py"))
 51
 52
 53def update_minimum_python_version(pyproject_path: Pathier):
 54    """Use vermin to determine the minimum compatible
 55    Python version and update the corresponding field
 56    in pyproject.toml."""
 57    project_code = get_project_code(pyproject_path.parent / "src")
 58    meta = pyproject_path.loads()
 59    minimum_version = get_minimum_py_version(project_code)
 60    minimum_version = f">={minimum_version}"
 61    meta["project"]["requires-python"] = minimum_version
 62    pyproject_path.dumps(meta)
 63
 64
 65def generate_docs(package_path: Pathier):
 66    """Generate project documentation using pdoc."""
 67    try:
 68        (package_path / "docs").delete()
 69    except Exception as e:
 70        pass
 71    os.system(
 72        f"pdoc -o {package_path / 'docs'} {package_path / 'src' / package_path.stem}"
 73    )
 74
 75
 76def update_dependencies(
 77    pyproject_path: Pathier, overwrite: bool, include_versions: bool = False
 78):
 79    """Update dependencies list in pyproject.toml.
 80
 81    :param overwrite: If True, replace the dependencies in pyproject.toml
 82    with the results of packagelister.scan() .
 83    If False, packages returned by packagelister are appended to
 84    the current dependencies in pyproject.toml if they don't already
 85    exist in the field."""
 86    packages = packagelister.scan(pyproject_path.parent)
 87
 88    packages = [
 89        f"{package}~={packages[package]['version']}"
 90        if packages[package]["version"] and include_versions
 91        else f"{package}"
 92        for package in packages
 93        if package != pyproject_path.parent.stem
 94    ]
 95    packages = [
 96        package.replace("speech_recognition", "speechRecognition")
 97        for package in packages
 98    ]
 99    meta = pyproject_path.loads()
100    if overwrite:
101        meta["project"]["dependencies"] = packages
102    else:
103        for package in packages:
104            if "~" in package:
105                name = package.split("~")[0]
106            elif "=" in package:
107                name = package.split("=")[0]
108            else:
109                name = package
110            if all(
111                name not in dependency for dependency in meta["project"]["dependencies"]
112            ):
113                meta["project"]["dependencies"].append(package)
114    pyproject_path.dumps(meta)
115
116
117def update_changelog(pyproject_path: Pathier):
118    """Update project changelog."""
119    meta = pyproject_path.loads()
120    if hassle_config.config_exists():
121        config = hassle_config.load_config()
122    else:
123        hassle_config.warn()
124        print("Creating blank hassle_config.toml...")
125        config = hassle_config.load_config()
126    changelog_path = pyproject_path.parent / "CHANGELOG.md"
127    os.system(
128        f"auto-changelog -p {pyproject_path.parent} --tag-prefix {config['git']['tag_prefix']} --unreleased -v {meta['project']['version']} -o {changelog_path}"
129    )
130    changelog = changelog_path.read_text().splitlines()
131    changelog = [line for line in changelog if "Full set of changes:" not in line]
132    changelog_path.write_text("\n".join(changelog))
133
134
135def tag_version(package_path: Pathier):
136    """Add a git tag corresponding
137    to the version number in pyproject.toml."""
138    if hassle_config.config_exists():
139        tag_prefix = hassle_config.load_config()["git"]["tag_prefix"]
140    else:
141        hassle_config.warn()
142        tag_prefix = ""
143    version = (package_path / "pyproject.toml").loads()["project"]["version"]
144    os.chdir(package_path)
145    git.tag(f"{tag_prefix}{version}")
146
147
148def format_files(path: Pathier):
149    """Use `Black` to format file(s)."""
150    try:
151        black.main([str(path)])
152    except SystemExit:
153        ...
def increment_version(pyproject_path: pathier.pathier.Pathier, increment_type: str):
15def increment_version(pyproject_path: Pathier, increment_type: str):
16    """Increment the project.version field in pyproject.toml.
17
18    :param package_path: Path to the package/project directory.
19
20    :param increment_type: One from 'major', 'minor', or 'patch'."""
21    meta = pyproject_path.loads()
22    major, minor, patch = [int(num) for num in meta["project"]["version"].split(".")]
23    if increment_type == "major":
24        major += 1
25        minor = 0
26        patch = 0
27    elif increment_type == "minor":
28        minor += 1
29        patch = 0
30    elif increment_type == "patch":
31        patch += 1
32    incremented_version = ".".join(str(num) for num in [major, minor, patch])
33    meta["project"]["version"] = incremented_version
34    pyproject_path.dumps(meta)

Increment the project.version field in pyproject.toml.

Parameters
  • package_path: Path to the package/project directory.

  • increment_type: One from 'major', 'minor', or 'patch'.

def get_minimum_py_version(src: str) -> str:
37def get_minimum_py_version(src: str) -> str:
38    """Scan src with vermin and return minimum
39    python version."""
40    config = vermin.Config()
41    config.add_backport("typing")
42    config.add_backport("typing_extensions")
43    config.set_eval_annotations(True)
44    result = vermin.visit(src, config).minimum_versions()[1]
45    return f"{result[0]}.{result[1]}"

Scan src with vermin and return minimum python version.

def get_project_code(project_path: pathier.pathier.Pathier) -> str:
48def get_project_code(project_path: Pathier) -> str:
49    """Read and return all code from project_path
50    as one string."""
51    return "\n".join(file.read_text() for file in project_path.rglob("*.py"))

Read and return all code from project_path as one string.

def update_minimum_python_version(pyproject_path: pathier.pathier.Pathier):
54def update_minimum_python_version(pyproject_path: Pathier):
55    """Use vermin to determine the minimum compatible
56    Python version and update the corresponding field
57    in pyproject.toml."""
58    project_code = get_project_code(pyproject_path.parent / "src")
59    meta = pyproject_path.loads()
60    minimum_version = get_minimum_py_version(project_code)
61    minimum_version = f">={minimum_version}"
62    meta["project"]["requires-python"] = minimum_version
63    pyproject_path.dumps(meta)

Use vermin to determine the minimum compatible Python version and update the corresponding field in pyproject.toml.

def generate_docs(package_path: pathier.pathier.Pathier):
66def generate_docs(package_path: Pathier):
67    """Generate project documentation using pdoc."""
68    try:
69        (package_path / "docs").delete()
70    except Exception as e:
71        pass
72    os.system(
73        f"pdoc -o {package_path / 'docs'} {package_path / 'src' / package_path.stem}"
74    )

Generate project documentation using pdoc.

def update_dependencies( pyproject_path: pathier.pathier.Pathier, overwrite: bool, include_versions: bool = False):
 77def update_dependencies(
 78    pyproject_path: Pathier, overwrite: bool, include_versions: bool = False
 79):
 80    """Update dependencies list in pyproject.toml.
 81
 82    :param overwrite: If True, replace the dependencies in pyproject.toml
 83    with the results of packagelister.scan() .
 84    If False, packages returned by packagelister are appended to
 85    the current dependencies in pyproject.toml if they don't already
 86    exist in the field."""
 87    packages = packagelister.scan(pyproject_path.parent)
 88
 89    packages = [
 90        f"{package}~={packages[package]['version']}"
 91        if packages[package]["version"] and include_versions
 92        else f"{package}"
 93        for package in packages
 94        if package != pyproject_path.parent.stem
 95    ]
 96    packages = [
 97        package.replace("speech_recognition", "speechRecognition")
 98        for package in packages
 99    ]
100    meta = pyproject_path.loads()
101    if overwrite:
102        meta["project"]["dependencies"] = packages
103    else:
104        for package in packages:
105            if "~" in package:
106                name = package.split("~")[0]
107            elif "=" in package:
108                name = package.split("=")[0]
109            else:
110                name = package
111            if all(
112                name not in dependency for dependency in meta["project"]["dependencies"]
113            ):
114                meta["project"]["dependencies"].append(package)
115    pyproject_path.dumps(meta)

Update dependencies list in pyproject.toml.

Parameters
  • overwrite: If True, replace the dependencies in pyproject.toml with the results of packagelister.scan() . If False, packages returned by packagelister are appended to the current dependencies in pyproject.toml if they don't already exist in the field.
def update_changelog(pyproject_path: pathier.pathier.Pathier):
118def update_changelog(pyproject_path: Pathier):
119    """Update project changelog."""
120    meta = pyproject_path.loads()
121    if hassle_config.config_exists():
122        config = hassle_config.load_config()
123    else:
124        hassle_config.warn()
125        print("Creating blank hassle_config.toml...")
126        config = hassle_config.load_config()
127    changelog_path = pyproject_path.parent / "CHANGELOG.md"
128    os.system(
129        f"auto-changelog -p {pyproject_path.parent} --tag-prefix {config['git']['tag_prefix']} --unreleased -v {meta['project']['version']} -o {changelog_path}"
130    )
131    changelog = changelog_path.read_text().splitlines()
132    changelog = [line for line in changelog if "Full set of changes:" not in line]
133    changelog_path.write_text("\n".join(changelog))

Update project changelog.

def tag_version(package_path: pathier.pathier.Pathier):
136def tag_version(package_path: Pathier):
137    """Add a git tag corresponding
138    to the version number in pyproject.toml."""
139    if hassle_config.config_exists():
140        tag_prefix = hassle_config.load_config()["git"]["tag_prefix"]
141    else:
142        hassle_config.warn()
143        tag_prefix = ""
144    version = (package_path / "pyproject.toml").loads()["project"]["version"]
145    os.chdir(package_path)
146    git.tag(f"{tag_prefix}{version}")

Add a git tag corresponding to the version number in pyproject.toml.

def format_files(path: pathier.pathier.Pathier):
149def format_files(path: Pathier):
150    """Use `Black` to format file(s)."""
151    try:
152        black.main([str(path)])
153    except SystemExit:
154        ...

Use Black to format file(s).