hassle.hassle
1import argparse 2import os 3import sys 4 5import isort 6from gitbetter import Git 7from pathier import Pathier 8 9from hassle import hassle_utilities 10from hassle.generate_tests import generate_test_files 11from hassle.run_tests import run_tests 12 13root = Pathier(__file__).parent 14 15 16def get_args() -> argparse.Namespace: 17 parser = argparse.ArgumentParser() 18 19 parser.add_argument( 20 "package", 21 type=str, 22 default=".", 23 nargs="?", 24 help=""" The name of the package or project to use, 25 assuming it's a subfolder of your current working directory. 26 Can also be a full path to the package. If nothing is given, 27 the current working directory will be used.""", 28 ) 29 30 parser.add_argument( 31 "-b", "--build", action="store_true", help=""" Build the package. """ 32 ) 33 34 parser.add_argument( 35 "-t", 36 "--tag_version", 37 action="store_true", 38 help=""" Add a git tag corresponding to the version in pyproject.toml. """, 39 ) 40 41 parser.add_argument( 42 "-i", 43 "--install", 44 action="store_true", 45 help=""" Install the package from source. """, 46 ) 47 48 parser.add_argument( 49 "-iv", 50 "--increment_version", 51 type=str, 52 default=None, 53 choices=["major", "minor", "patch"], 54 help=""" Increment version in pyproject.toml. 55 Can be one of "major", "minor", or "patch". """, 56 ) 57 58 parser.add_argument( 59 "-p", 60 "--publish", 61 action="store_true", 62 help=""" Publish package to PyPi. 63 Note: You must have configured twine 64 and registered a PyPi account/generated an API 65 key to use this option.""", 66 ) 67 68 parser.add_argument( 69 "-rt", 70 "--run_tests", 71 action="store_true", 72 help=""" Run tests for the package. """, 73 ) 74 75 parser.add_argument( 76 "-gt", 77 "--generate_tests", 78 action="store_true", 79 help=""" Generate tests for the package. """, 80 ) 81 82 parser.add_argument( 83 "-uc", 84 "--update_changelog", 85 action="store_true", 86 help=""" Update changelog file. """, 87 ) 88 89 parser.add_argument( 90 "-od", 91 "--overwrite_dependencies", 92 action="store_true", 93 help=""" When building a package, packagelister will be used 94 to update the dependencies list in pyproject.toml. 95 The default behavior is to append any new dependencies to 96 the current list so as not to erase any manually added dependencies 97 that packagelister may not detect. If you don't have any manually 98 added dependencies and want to remove any dependencies that your 99 project no longer uses, pass this flag.""", 100 ) 101 102 parser.add_argument( 103 "-ca", 104 "--commit_all", 105 type=str, 106 default=None, 107 help=""" Git stage and commit all tracked files with this supplied commit message. 108 If 'build' is passed, all commits will have message: 'chore: build v{current_version}""", 109 ) 110 111 parser.add_argument( 112 "-s", 113 "--sync", 114 action="store_true", 115 help=""" Pull from github, then push current commit to repo. """, 116 ) 117 118 parser.add_argument( 119 "-dv", 120 "--dependency_versions", 121 action="store_true", 122 help=""" Include version specifiers for dependencies in 123 pyproject.toml.""", 124 ) 125 126 parser.add_argument( 127 "-up", 128 "--update", 129 type=str, 130 default=None, 131 choices=["major", "minor", "patch"], 132 help=""" Excpects one argument: "major", "minor", or "patch". 133 Passing "-up minor" is equivalent to passing "--build --tag_version --increment_version minor --update_changelog --commit_all build --sync". 134 To publish the updated package, the -p/--publish switch needs to be added to the cli input. 135 To install the updated package, the -i/--install switch also needs to be added.""", 136 ) 137 138 parser.add_argument( 139 "-st", 140 "--skip_tests", 141 action="store_true", 142 help=""" Don't run tests when using the -b/--build command. """, 143 ) 144 145 args = parser.parse_args() 146 147 args.package = Pathier(args.package).resolve() 148 149 if args.update: 150 args.build = True 151 args.tag_version = True 152 args.increment_version = args.update 153 args.update_changelog = True 154 args.commit_all = "build" 155 args.sync = True 156 157 if args.increment_version and args.increment_version not in [ 158 "major", 159 "minor", 160 "patch", 161 ]: 162 raise ValueError( 163 f"Invalid option for -iv/--increment_version: {args.increment_version}" 164 ) 165 166 if args.commit_all == "": 167 raise ValueError("Commit message for args.commit_all cannot be empty.") 168 169 if args.publish and not hassle_utilities.on_primary_branch(): 170 print( 171 "WARNING: You are trying to publish a project that does not appear to be on its main branch." 172 ) 173 choice = input("Continue? (y/n) ") 174 if choice != "y": 175 print("Quitting hassle.") 176 sys.exit() 177 178 return args 179 180 181def build( 182 package_dir: Pathier, 183 skip_tests: bool = False, 184 overwrite_dependencies: bool = False, 185 increment_version: str | None = None, 186): 187 """Perform the build process. 188 189 Steps: 190 * Run tests (unless `skip_tests` is `True`) 191 * Raise error and abandon build if tests fail 192 * Format source code with `Black` 193 * Sort source code imports with `isort` 194 * Update project dependencies in `pyproject.toml` 195 * Increment version in `pyproject.toml` if `increment_version` supplied 196 * Generate docs 197 * Delete previous `dist` folder contents 198 * Invoke build module""" 199 if not skip_tests and not run_tests(package_dir): 200 raise RuntimeError( 201 f"ERROR: {package_dir.stem} failed testing.\nAbandoning build." 202 ) 203 hassle_utilities.format_files(package_dir) 204 [isort.file(path) for path in package_dir.rglob("*.py")] 205 hassle_utilities.update_dependencies( 206 package_dir / "pyproject.toml", overwrite_dependencies 207 ) 208 if increment_version: 209 hassle_utilities.increment_version( 210 package_dir / "pyproject.toml", increment_version 211 ) 212 # Vermin isn't taking into account the minimum version of dependencies. 213 # Removing from now and defaulting to >=3.10 214 # hassle_utilities.update_minimum_python_version(pyproject_path) 215 hassle_utilities.generate_docs(package_dir) 216 (package_dir / "dist").delete() 217 os.system(f"{sys.executable} -m build {package_dir}") 218 219 220def main(args: argparse.Namespace = None): 221 if not args: 222 args = get_args() 223 224 pyproject_path = args.package / "pyproject.toml" 225 args.package.mkcwd() 226 227 git = Git() 228 229 if not pyproject_path.exists(): 230 raise FileNotFoundError(f"Could not locate pyproject.toml for {args.package}") 231 232 if args.generate_tests: 233 generate_test_files(args.package) 234 235 if args.run_tests: 236 run_tests(args.package) 237 238 if args.build: 239 build( 240 args.package, 241 args.skip_tests, 242 args.overwrite_dependencies, 243 args.increment_version, 244 ) 245 246 if args.increment_version and not args.build: 247 hassle_utilities.increment_version(pyproject_path, args.increment_version) 248 249 if args.commit_all: 250 if args.commit_all == "build": 251 version = pyproject_path.loads()["project"]["version"] 252 args.commit_all = f"chore: build v{version}" 253 git.add() 254 git.commit(f'-m "{args.commit_all}"') 255 256 if args.tag_version: 257 hassle_utilities.tag_version(args.package) 258 259 if args.update_changelog: 260 hassle_utilities.update_changelog(pyproject_path) 261 if args.tag_version: 262 with git.capture_output(): 263 tags = git.tag("--sort=-committerdate") 264 most_recent_tag = tags[: tags.find("\n")] 265 git.execute(f"tag -d {most_recent_tag}") 266 input("Press enter to continue after manually adjusting the changelog...") 267 git.commit_files( 268 [str(args.package / "CHANGELOG.md")], "chore: update changelog" 269 ) 270 if args.tag_version: 271 with git.capture_output(): 272 git.tag(most_recent_tag) 273 274 if args.publish: 275 os.system(f"twine upload {args.package / 'dist' / '*'}") 276 277 if args.install: 278 os.system( 279 f"{sys.executable} -m pip install {args.package} --no-deps --upgrade --no-cache-dir" 280 ) 281 282 if args.sync: 283 git.pull("--tags") 284 git.push("--tags") 285 286 287if __name__ == "__main__": 288 main(get_args())
def
get_args() -> argparse.Namespace:
17def get_args() -> argparse.Namespace: 18 parser = argparse.ArgumentParser() 19 20 parser.add_argument( 21 "package", 22 type=str, 23 default=".", 24 nargs="?", 25 help=""" The name of the package or project to use, 26 assuming it's a subfolder of your current working directory. 27 Can also be a full path to the package. If nothing is given, 28 the current working directory will be used.""", 29 ) 30 31 parser.add_argument( 32 "-b", "--build", action="store_true", help=""" Build the package. """ 33 ) 34 35 parser.add_argument( 36 "-t", 37 "--tag_version", 38 action="store_true", 39 help=""" Add a git tag corresponding to the version in pyproject.toml. """, 40 ) 41 42 parser.add_argument( 43 "-i", 44 "--install", 45 action="store_true", 46 help=""" Install the package from source. """, 47 ) 48 49 parser.add_argument( 50 "-iv", 51 "--increment_version", 52 type=str, 53 default=None, 54 choices=["major", "minor", "patch"], 55 help=""" Increment version in pyproject.toml. 56 Can be one of "major", "minor", or "patch". """, 57 ) 58 59 parser.add_argument( 60 "-p", 61 "--publish", 62 action="store_true", 63 help=""" Publish package to PyPi. 64 Note: You must have configured twine 65 and registered a PyPi account/generated an API 66 key to use this option.""", 67 ) 68 69 parser.add_argument( 70 "-rt", 71 "--run_tests", 72 action="store_true", 73 help=""" Run tests for the package. """, 74 ) 75 76 parser.add_argument( 77 "-gt", 78 "--generate_tests", 79 action="store_true", 80 help=""" Generate tests for the package. """, 81 ) 82 83 parser.add_argument( 84 "-uc", 85 "--update_changelog", 86 action="store_true", 87 help=""" Update changelog file. """, 88 ) 89 90 parser.add_argument( 91 "-od", 92 "--overwrite_dependencies", 93 action="store_true", 94 help=""" When building a package, packagelister will be used 95 to update the dependencies list in pyproject.toml. 96 The default behavior is to append any new dependencies to 97 the current list so as not to erase any manually added dependencies 98 that packagelister may not detect. If you don't have any manually 99 added dependencies and want to remove any dependencies that your 100 project no longer uses, pass this flag.""", 101 ) 102 103 parser.add_argument( 104 "-ca", 105 "--commit_all", 106 type=str, 107 default=None, 108 help=""" Git stage and commit all tracked files with this supplied commit message. 109 If 'build' is passed, all commits will have message: 'chore: build v{current_version}""", 110 ) 111 112 parser.add_argument( 113 "-s", 114 "--sync", 115 action="store_true", 116 help=""" Pull from github, then push current commit to repo. """, 117 ) 118 119 parser.add_argument( 120 "-dv", 121 "--dependency_versions", 122 action="store_true", 123 help=""" Include version specifiers for dependencies in 124 pyproject.toml.""", 125 ) 126 127 parser.add_argument( 128 "-up", 129 "--update", 130 type=str, 131 default=None, 132 choices=["major", "minor", "patch"], 133 help=""" Excpects one argument: "major", "minor", or "patch". 134 Passing "-up minor" is equivalent to passing "--build --tag_version --increment_version minor --update_changelog --commit_all build --sync". 135 To publish the updated package, the -p/--publish switch needs to be added to the cli input. 136 To install the updated package, the -i/--install switch also needs to be added.""", 137 ) 138 139 parser.add_argument( 140 "-st", 141 "--skip_tests", 142 action="store_true", 143 help=""" Don't run tests when using the -b/--build command. """, 144 ) 145 146 args = parser.parse_args() 147 148 args.package = Pathier(args.package).resolve() 149 150 if args.update: 151 args.build = True 152 args.tag_version = True 153 args.increment_version = args.update 154 args.update_changelog = True 155 args.commit_all = "build" 156 args.sync = True 157 158 if args.increment_version and args.increment_version not in [ 159 "major", 160 "minor", 161 "patch", 162 ]: 163 raise ValueError( 164 f"Invalid option for -iv/--increment_version: {args.increment_version}" 165 ) 166 167 if args.commit_all == "": 168 raise ValueError("Commit message for args.commit_all cannot be empty.") 169 170 if args.publish and not hassle_utilities.on_primary_branch(): 171 print( 172 "WARNING: You are trying to publish a project that does not appear to be on its main branch." 173 ) 174 choice = input("Continue? (y/n) ") 175 if choice != "y": 176 print("Quitting hassle.") 177 sys.exit() 178 179 return args
def
build( package_dir: pathier.pathier.Pathier, skip_tests: bool = False, overwrite_dependencies: bool = False, increment_version: str | None = None):
182def build( 183 package_dir: Pathier, 184 skip_tests: bool = False, 185 overwrite_dependencies: bool = False, 186 increment_version: str | None = None, 187): 188 """Perform the build process. 189 190 Steps: 191 * Run tests (unless `skip_tests` is `True`) 192 * Raise error and abandon build if tests fail 193 * Format source code with `Black` 194 * Sort source code imports with `isort` 195 * Update project dependencies in `pyproject.toml` 196 * Increment version in `pyproject.toml` if `increment_version` supplied 197 * Generate docs 198 * Delete previous `dist` folder contents 199 * Invoke build module""" 200 if not skip_tests and not run_tests(package_dir): 201 raise RuntimeError( 202 f"ERROR: {package_dir.stem} failed testing.\nAbandoning build." 203 ) 204 hassle_utilities.format_files(package_dir) 205 [isort.file(path) for path in package_dir.rglob("*.py")] 206 hassle_utilities.update_dependencies( 207 package_dir / "pyproject.toml", overwrite_dependencies 208 ) 209 if increment_version: 210 hassle_utilities.increment_version( 211 package_dir / "pyproject.toml", increment_version 212 ) 213 # Vermin isn't taking into account the minimum version of dependencies. 214 # Removing from now and defaulting to >=3.10 215 # hassle_utilities.update_minimum_python_version(pyproject_path) 216 hassle_utilities.generate_docs(package_dir) 217 (package_dir / "dist").delete() 218 os.system(f"{sys.executable} -m build {package_dir}")
Perform the build process.
Steps:
- Run tests (unless
skip_tests
isTrue
) - Raise error and abandon build if tests fail
- Format source code with
Black
- Sort source code imports with
isort
- Update project dependencies in
pyproject.toml
- Increment version in
pyproject.toml
ifincrement_version
supplied - Generate docs
- Delete previous
dist
folder contents - Invoke build module
def
main(args: argparse.Namespace = None):
221def main(args: argparse.Namespace = None): 222 if not args: 223 args = get_args() 224 225 pyproject_path = args.package / "pyproject.toml" 226 args.package.mkcwd() 227 228 git = Git() 229 230 if not pyproject_path.exists(): 231 raise FileNotFoundError(f"Could not locate pyproject.toml for {args.package}") 232 233 if args.generate_tests: 234 generate_test_files(args.package) 235 236 if args.run_tests: 237 run_tests(args.package) 238 239 if args.build: 240 build( 241 args.package, 242 args.skip_tests, 243 args.overwrite_dependencies, 244 args.increment_version, 245 ) 246 247 if args.increment_version and not args.build: 248 hassle_utilities.increment_version(pyproject_path, args.increment_version) 249 250 if args.commit_all: 251 if args.commit_all == "build": 252 version = pyproject_path.loads()["project"]["version"] 253 args.commit_all = f"chore: build v{version}" 254 git.add() 255 git.commit(f'-m "{args.commit_all}"') 256 257 if args.tag_version: 258 hassle_utilities.tag_version(args.package) 259 260 if args.update_changelog: 261 hassle_utilities.update_changelog(pyproject_path) 262 if args.tag_version: 263 with git.capture_output(): 264 tags = git.tag("--sort=-committerdate") 265 most_recent_tag = tags[: tags.find("\n")] 266 git.execute(f"tag -d {most_recent_tag}") 267 input("Press enter to continue after manually adjusting the changelog...") 268 git.commit_files( 269 [str(args.package / "CHANGELOG.md")], "chore: update changelog" 270 ) 271 if args.tag_version: 272 with git.capture_output(): 273 git.tag(most_recent_tag) 274 275 if args.publish: 276 os.system(f"twine upload {args.package / 'dist' / '*'}") 277 278 if args.install: 279 os.system( 280 f"{sys.executable} -m pip install {args.package} --no-deps --upgrade --no-cache-dir" 281 ) 282 283 if args.sync: 284 git.pull("--tags") 285 git.push("--tags")