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 -i -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.install = 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 170 171 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}") 209 210 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 if not pyproject_path.exists(): 219 raise FileNotFoundError(f"Could not locate pyproject.toml for {args.package}") 220 221 if args.generate_tests: 222 generate_test_files(args.package) 223 224 if args.run_tests: 225 run_tests(args.package) 226 227 if args.build: 228 build( 229 args.package, 230 args.skip_tests, 231 args.overwrite_dependencies, 232 args.increment_version, 233 ) 234 235 if args.increment_version and not args.build: 236 hassle_utilities.increment_version(pyproject_path, args.increment_version) 237 238 if args.update_changelog: 239 hassle_utilities.update_changelog(pyproject_path) 240 # If we're going to add tag for current version 241 # commit changelog first 242 if args.tag_version: 243 input( 244 "Press enter to continue after optionally pruning the updated changelog..." 245 ) 246 git.commit_files( 247 [str(args.package / "CHANGELOG.md")], "chore: update changelog" 248 ) 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.publish: 261 os.system(f"twine upload {args.package / 'dist' / '*'}") 262 263 if args.install: 264 os.system( 265 f"{sys.executable} -m pip install {args.package} --no-deps --upgrade --no-cache-dir" 266 ) 267 268 if args.sync: 269 git.pull("origin main --tags") 270 git.push("origin main --tags") 271 272 273if __name__ == "__main__": 274 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 -i -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.install = 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 return args
def
build( package_dir: pathier.pathier.Pathier, skip_tests: bool = False, overwrite_dependencies: bool = False, increment_version: str | None = None):
173def build( 174 package_dir: Pathier, 175 skip_tests: bool = False, 176 overwrite_dependencies: bool = False, 177 increment_version: str | None = None, 178): 179 """Perform the build process. 180 181 Steps: 182 * Run tests (unless `skip_tests` is `True`) 183 * Raise error and abandon build if tests fail 184 * Format source code with `Black` 185 * Sort source code imports with `isort` 186 * Update project dependencies in `pyproject.toml` 187 * Increment version in `pyproject.toml` if `increment_version` supplied 188 * Generate docs 189 * Delete previous `dist` folder contents 190 * Invoke build module""" 191 if not skip_tests and not run_tests(package_dir): 192 raise RuntimeError( 193 f"ERROR: {package_dir.stem} failed testing.\nAbandoning build." 194 ) 195 hassle_utilities.format_files(package_dir) 196 [isort.file(path) for path in package_dir.rglob("*.py")] 197 hassle_utilities.update_dependencies( 198 package_dir / "pyproject.toml", overwrite_dependencies 199 ) 200 if increment_version: 201 hassle_utilities.increment_version( 202 package_dir / "pyproject.toml", increment_version 203 ) 204 # Vermin isn't taking into account the minimum version of dependencies. 205 # Removing from now and defaulting to >=3.10 206 # hassle_utilities.update_minimum_python_version(pyproject_path) 207 hassle_utilities.generate_docs(package_dir) 208 (package_dir / "dist").delete() 209 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):
212def main(args: argparse.Namespace = None): 213 if not args: 214 args = get_args() 215 216 pyproject_path = args.package / "pyproject.toml" 217 args.package.mkcwd() 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.update_changelog: 240 hassle_utilities.update_changelog(pyproject_path) 241 # If we're going to add tag for current version 242 # commit changelog first 243 if args.tag_version: 244 input( 245 "Press enter to continue after optionally pruning the updated changelog..." 246 ) 247 git.commit_files( 248 [str(args.package / "CHANGELOG.md")], "chore: update changelog" 249 ) 250 251 if args.commit_all: 252 if args.commit_all == "build": 253 version = pyproject_path.loads()["project"]["version"] 254 args.commit_all = f"chore: build v{version}" 255 git.add() 256 git.commit(f'-m "{args.commit_all}"') 257 258 if args.tag_version: 259 hassle_utilities.tag_version(args.package) 260 261 if args.publish: 262 os.system(f"twine upload {args.package / 'dist' / '*'}") 263 264 if args.install: 265 os.system( 266 f"{sys.executable} -m pip install {args.package} --no-deps --upgrade --no-cache-dir" 267 ) 268 269 if args.sync: 270 git.pull("origin main --tags") 271 git.push("origin main --tags")