""" To use this module, install invoke and type invoke -l """ from functools import partial import os from pathlib import Path from setuptools.config import read_configuration from invoke import task, Exit, Task as Task_, call def activate_venv(c, venv: str): """ Activate a virtualenv """ virtual_env = Path().absolute() / venv if original_path := os.environ.get("PATH"): path = f'{virtual_env / "bin"}:{original_path}' else: path = str(virtual_env / "bin") c.config.run.env["PATH"] = path c.config.run.env["VIRTUAL_ENV"] = str(virtual_env) os.environ.pop("PYTHONHOME", "") def title(text, underline_char="#"): """ Display text as a title. """ template = f"{{:{underline_char}^80}}" text = template.format(f" {text.strip()} ") print(f"\033[1m{text}\033[0m") class Task(Task_): """ This task add 'skip_if_recent' feature. >>> @task(skip_if_recent=['./target', './dependency']) >>> def my_tash(c): >>> ... target is file created by the task dependency is file used by the task The task is ran only if the dependency is more recent than the target file. The target or the dependency can be a tuple of files. """ def __init__(self, *args, **kwargs): self.skip_if_recent = kwargs.pop("skip_if_recent", None) super().__init__(*args, **kwargs) def __call__(self, *args, **kwargs): title(self.__doc__ or self.name) if self.skip_if_recent: targets, dependencies = self.skip_if_recent if isinstance(targets, str): targets = (targets,) if isinstance(dependencies, str): dependencies = (dependencies,) target_mtime = min( ((Path(file).exists() and Path(file).lstat().st_mtime) or 0) for file in targets ) dependency_mtime = max(Path(file).lstat().st_mtime for file in dependencies) if dependency_mtime < target_mtime: print(f"{self.name}, nothing to do") return None return super().__call__(*args, **kwargs) task = partial(task, klass=Task) @task() def venv(c): """ Create a virtual environment for dev """ c.run("python -m venv --clear venv") c.run("venv/bin/pip install -U setuptools wheel pip") c.run("venv/bin/pip install -e .") c.run("venv/bin/pip install -r requirements/test.txt") @task() def check_readme(c): """ Check the README.rst render """ c.run("python -m readme_renderer -o /dev/null README.rst") @task() def test(c, isolate=False): """ Launch tests """ opt = "I" if isolate else "" c.run(f"python -{opt}m pytest --cov-report=xml --cov=aiohttp_pydantic tests/") @task() def tag_eq_version(c): """ Ensure that the last git tag matches the package version """ git_tag = c.run("git describe --tags HEAD", hide=True).stdout.strip() package_version = read_configuration("./setup.cfg")["metadata"]["version"] if git_tag != f"v{package_version}": raise Exit( f"ERROR: The git tag {git_tag!r} does not matches" f" the package version {package_version!r}" ) @task() def prepare_ci_env(c): """ Prepare CI environment """ title("Creating virtual env", "=") c.run("python -m venv --clear dist_venv") activate_venv(c, "dist_venv") c.run("dist_venv/bin/python -m pip install -U setuptools wheel pip") title("Building wheel", "=") c.run("dist_venv/bin/python setup.py build bdist_wheel") title("Installing wheel", "=") package_version = read_configuration("./setup.cfg")["metadata"]["version"] dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl")) c.run(f"dist_venv/bin/python -m pip install {dist}") # We verify that aiohttp-pydantic module is importable before installing CI tools. package_names = read_configuration("./setup.cfg")["options"]["packages"] for package_name in package_names: c.run(f"dist_venv/bin/python -I -c 'import {package_name}'") title("Installing CI tools", "=") c.run("dist_venv/bin/python -m pip install -r requirements/ci.txt") @task(prepare_ci_env, check_readme, call(test, isolate=True), klass=Task_) def prepare_upload(c): """ Launch all tests and verifications """ @task(tag_eq_version, prepare_upload) def upload(c, pypi_user=None, pypi_password=None): """ Upload on pypi """ package_version = read_configuration("./setup.cfg")["metadata"]["version"] dist = next(Path("dist").glob(f"aiohttp_pydantic-{package_version}-*.whl")) if pypi_user is not None and pypi_password is not None: c.run( f"dist_venv/bin/twine upload --non-interactive" f" -u {pypi_user} -p {pypi_password} {dist}", hide=True, ) else: c.run(f"dist_venv/bin/twine upload --repository aiohttp-pydantic {dist}")