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