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