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 is True)
  • 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 if increment_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")