def testenv_coverage(self): """ ``[testenv:coverage]``. """ if self["enable_tests"]: self._ini["testenv:coverage"]["basepython"] = f"python{self['min_py_version']}" self._ini["testenv:coverage"]["skip_install"] = True self._ini["testenv:coverage"]["ignore_errors"] = True self._ini["testenv:coverage"]["whitelist_externals"] = "/bin/bash" self._ini["testenv:coverage"]["passenv"] = indent_join([ "COV_PYTHON_VERSION", "COV_PLATFORM", "COV_PYTHON_IMPLEMENTATION", ]) self._ini["testenv:coverage"]["changedir"] = "{toxinidir}" coverage_deps = ["coverage>=5"] if self["pypi_name"] != "coverage_pyver_pragma": coverage_deps.append("coverage_pyver_pragma>=0.2.1") self._ini["testenv:coverage"]["deps"] = indent_join(coverage_deps) self._ini["testenv:coverage"]["commands"] = indent_join([ '/bin/bash -c "rm -rf htmlcov"', "coverage html", "/bin/bash -c \"DISPLAY=:0 firefox 'htmlcov/index.html'\"", ]) else: self._ini.remove_section("testenv:coverage")
def testenv_docs(self): """ ``[testenv:docs]``. """ if self["enable_docs"]: envvars = ["SHOW_TODOS = 1"] self._ini["testenv:docs"]["setenv"] = indent_join(envvars) self._ini["testenv:docs"]["basepython"] = "python3.8" self._ini["testenv:docs"]["pip_version"] = "pip>=21" self._ini["testenv:docs"]["changedir"] = f"{{toxinidir}}/{self['docs_dir']}" if self["tox_testenv_extras"]: self._ini["testenv:docs"]["extras"] = self["tox_testenv_extras"] self._ini["testenv:docs"]["deps"] = f"-r{{toxinidir}}/{self['docs_dir']}/requirements.txt" # self._ini["testenv:docs"]["deps"] = indent_join([ # "-r{toxinidir}/requirements.txt", # f"-r{{toxinidir}}/{self['docs_dir']}/requirements.txt", # ], ) self._ini["testenv:docs"]["commands"] = "sphinx-build -M {env:SPHINX_BUILDER:html} . ./build {posargs}" else: self._ini.remove_section("testenv:docs")
def options_packages_find(self): """ ``[options.packages.find]``. """ excludes = [ self["tests_dir"], f"{self['tests_dir']}.*", self["docs_dir"] ] self._ini["options.packages.find"]["exclude"] = indent_join( sorted(set(excludes)))
def testenv_mypy(self): """ ``[testenv:mypy]``. """ self._ini["testenv:mypy"]["basepython"] = "python{python_deploy_version}".format(**self._globals) self._ini["testenv:mypy"]["ignore_errors"] = True self._ini["testenv:mypy"]["changedir"] = "{toxinidir}" if self._globals["tox_testenv_extras"]: self._ini["testenv:mypy"]["extras"] = self._globals["tox_testenv_extras"] self._ini["testenv:mypy"]["deps"] = indent_join(self.get_mypy_dependencies()) commands = self.get_mypy_commands() if commands: self._ini["testenv:mypy"]["commands"] = indent_join(commands) else: self._ini.remove_section("testenv:mypy")
def testenv_build(self): """ ``[testenv:build]``. """ self._ini["testenv:build"]["skip_install"] = True self._ini["testenv:build"]["changedir"] = "{toxinidir}" self._ini["testenv:build"]["deps"] = indent_join([ "build[virtualenv]>=0.3.1", "check-wheel-contents>=0.1.0", "twine>=3.2.0", *self["tox_build_requirements"], ]) self._ini["testenv:build"]["commands"] = indent_join([ 'python -m build --sdist --wheel "{toxinidir}"', # python setup.py {posargs} sdist bdist_wheel # "twine check dist/*", "twine check dist/*.tar.gz dist/*.whl", # source "check-wheel-contents dist/", ])
def flake8(self): """ ``[flake8]``. """ test_ignores = list(code_only_warning) test_ignores.remove("E301") test_ignores.remove("E302") test_ignores.remove("E305") self._ini["flake8"]["max-line-length"] = "120" self._ini["flake8"]["select"] = f"{DelimitedList(lint_warn_list + code_only_warning): }" self._ini["flake8"]["extend-exclude"] = ','.join([self["docs_dir"], *standard_flake8_excludes]) self._ini["flake8"]["rst-directives"] = indent_join(sorted(allowed_rst_directives)) self._ini["flake8"]["rst-roles"] = indent_join(sorted(allowed_rst_roles)) self._ini["flake8"]["per-file-ignores"] = indent_join([ '', f"{self['tests_dir']}/*: {' '.join(str(e) for e in test_ignores)}", f"*/*.pyi: {' '.join(str(e) for e in code_only_warning)}", ]) self._ini["flake8"]["pytest-parametrize-names-type"] = "csv" self._ini["flake8"]["inline-quotes"] = '"' self._ini["flake8"]["multiline-quotes"] = '"""' self._ini["flake8"]["docstring-quotes"] = '"""' self._ini["flake8"]["count"] = True if self["requires_python"] is None: if self["min_py_version"] in {"3.6", 3.6}: requires_python = "3.6.1" else: requires_python = self["min_py_version"] else: requires_python = self["requires_python"] self._ini["flake8"]["min_python_version"] = requires_python self._ini["flake8"]["unused-arguments-ignore-abstract-functions"] = True self._ini["flake8"]["unused-arguments-ignore-overload-functions"] = True self._ini["flake8"]["unused-arguments-ignore-magic-methods"] = True self._ini["flake8"]["unused-arguments-ignore-variadic-names"] = True
def coverage_report(self): """ ``[coverage:report]``. """ self._ini["coverage:report"]["fail_under"] = self["min_coverage"] self._ini["coverage:report"]["exclude_lines"] = indent_join([ "raise AssertionError", "raise NotImplementedError", "if 0:", "if False:", "if TYPE_CHECKING:", "if typing.TYPE_CHECKING:", "if __name__ == .__main__.:", ])
def merge_existing(self, ini_file): if ini_file.is_file(): existing_config = ConfigUpdater() existing_config.read(str(ini_file)) for section in existing_config.sections_blocks(): if section.name == "options.packages.find" and "exclude" in section: all_excludes = ( *section["exclude"].value.splitlines(), *self._ini["options.packages.find"] ["exclude"].value.splitlines(), ) exclude_packages = sorted( filter(bool, set(map(str.strip, all_excludes)))) self._ini["options.packages.find"][ "exclude"] = indent_join(exclude_packages) if section.name not in self.managed_sections: self._ini.add_section(section) elif section.name == "mypy": self.copy_existing_value(section, "incremental") if "options.entry_points" not in self._ini.sections(): self._ini.add_section("options.entry_points") # if self["console_scripts"]: # self._ini["options.entry_points"]["console_scripts"] = self["console_scripts"] # else: if not self._ini["options.entry_points"].options(): self._ini.remove_section("options.entry_points") if self["use_whey"]: self._ini.remove_section("metadata") self._ini.remove_section("options") self._ini.remove_section("options.packages.find") if float(self["mypy_version"]) >= 0.901: self._ini.remove_section("mypy")
def testenv_lint(self): """ ``[testenv:lint]``. """ self._ini["testenv:lint"]["basepython"] = "python{python_deploy_version}".format(**self._globals) self._ini["testenv:lint"]["changedir"] = "{toxinidir}" self._ini["testenv:lint"]["ignore_errors"] = True if self["pypi_name"] in {"domdf_python_tools", "consolekit"}: self._ini["testenv:lint"]["skip_install"] = False elif self["pypi_name"].startswith("flake8"): self._ini["testenv:lint"]["skip_install"] = False else: self._ini["testenv:lint"]["skip_install"] = True self._ini["testenv:lint"]["deps"] = indent_join([ "flake8>=3.8.2", "flake8-2020>=1.6.0", "flake8-builtins>=1.5.3", "flake8-docstrings>=1.5.0", "flake8-dunder-all>=0.1.1", "flake8-encodings>=0.1.0", "flake8-github-actions>=0.1.0", "flake8-noqa>=1.1.0", "flake8-pyi>=20.10.0", "flake8-pytest-style>=1.3.0", "flake8-quotes>=3.3.0", "flake8-slots>=0.1.0", "flake8-sphinx-links>=0.0.4", "flake8-strftime>=0.1.1", "flake8-typing-imports>=1.10.0", "git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git", "git+https://github.com/domdfcoding/flake8-rst-docstrings.git", "git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods", "pydocstyle>=6.0.0", "pygments>=2.7.1", "importlib_metadata<4.5.0; python_version<'3.8'" ]) cmd = f"python3 -m flake8_rst_docstrings_sphinx {' '.join(self.get_source_files())} --allow-toolbox {{posargs}}" self._ini["testenv:lint"]["commands"] = cmd
def tox(self): """ ``[tox]``. """ tox_envs: List[str] = [] if self["third_party_version_matrix"]: for third_party_library in self["third_party_version_matrix"]: third_party_versions = DelimitedList(self["third_party_version_matrix"][third_party_library]) matrix_testenv_string = f"-{third_party_library}{{{third_party_versions:,}}}" tox_envs.extend(v + matrix_testenv_string for v in self["tox_py_versions"]) else: tox_envs = self["tox_py_versions"] self._ini["tox"]["envlist"] = [*tox_envs, "mypy", "build"] self._ini["tox"]["skip_missing_interpreters"] = True self._ini["tox"]["isolated_build"] = True tox_requires = {"pip>=20.3.3", "tox-pip-version>=0.0.7", *self["tox_requirements"]} if self["pypi_name"] != "tox-envlist": tox_requires.add("tox-envlist>=0.2.1") self._ini["tox"]["requires"] = indent_join(sorted(tox_requires))
def merge_existing(self, ini_file): """ Merge existing sections in the configuration file into the new configuration. :param ini_file: The existing ``.ini`` file. """ if ini_file.is_file(): existing_config = ConfigUpdater() existing_config.read(str(ini_file)) for section in existing_config.sections_blocks(): if section.name not in self.managed_sections: self._ini.add_section(section) elif section.name == "coverage:report" and "omit" in section: self._ini["coverage:report"]["omit"] = section["omit"].value elif section.name == "flake8": if "rst-directives" in section: existing_directives = section["rst-directives"].value.splitlines() new_directives = self._ini["flake8"]["rst-directives"].value.splitlines() combined_directives = set(map(str.strip, (*new_directives, *existing_directives))) self._ini["flake8"]["rst-directives"] = indent_join( sorted(filter(bool, combined_directives)) ) if "rst-roles" in section: existing_roles = section["rst-roles"].value.splitlines() new_roles = self._ini["flake8"]["rst-roles"].value.splitlines() combined_roles = set(map(str.strip, (*new_roles, *existing_roles))) self._ini["flake8"]["rst-roles"] = indent_join(sorted(filter(bool, combined_roles))) if "per-file-ignores" in section: combined_ignores = {} # Existing first, so they're always overridden by our new ones for line in section["per-file-ignores"].value.splitlines(): if not line.strip(): continue glob, ignores = line.split(':', 1) combined_ignores[glob.strip()] = ignores.strip() for line in self._ini["flake8"]["per-file-ignores"].value.splitlines(): if not line.strip(): continue glob, ignores = line.split(':', 1) combined_ignores[glob.strip()] = ignores.strip() # Always put tests/* and */*.pyi first combined_ignores_strings = [ f"tests/*: {combined_ignores.pop('tests/*')}", f"*/*.pyi: {combined_ignores.pop('*/*.pyi')}", ] combined_ignores_strings.extend( sorted(filter(bool, (map(": ".join, combined_ignores.items())))) ) self._ini["flake8"]["per-file-ignores"] = indent_join(combined_ignores_strings) elif section.name == "pytest": if "filterwarnings" in section: existing_value = set(map(str.strip, section["filterwarnings"].value.splitlines())) self._ini["pytest"]["filterwarnings"] = indent_join(sorted(filter(bool, existing_value))) if "markers" in section: existing_value = set(map(str.strip, section["markers"].value.splitlines())) self._ini["pytest"]["markers"] = indent_join(sorted(filter(bool, existing_value)))
def testenv(self): """ ``[testenv]``. """ if self["enable_devmode"]: self._ini["testenv"]["setenv"] = indent_join(("PYTHONDEVMODE=1", "PIP_DISABLE_PIP_VERSION_CHECK=1")) if self["enable_tests"]: deps = [f"-r{{toxinidir}}/{self['tests_dir']}/requirements.txt"] if self["third_party_version_matrix"]: for third_party_library in self["third_party_version_matrix"].keys(): for version in self["third_party_version_matrix"][third_party_library]: if version == "latest": deps.append(f"{third_party_library}latest: {third_party_library}") else: v = Version(version) if v.is_prerelease: deps.append(f"{third_party_library}{version}: {third_party_library}=={version}") else: deps.append(f"{third_party_library}{version}: {third_party_library}~={version}.0") self._ini["testenv"]["deps"] = indent_join(deps) elif not self["stubs_package"]: deps = ["importcheck>=0.1.0"] if self["third_party_version_matrix"]: third_party_library = list(self["third_party_version_matrix"].keys())[0] for version in self["third_party_version_matrix"][third_party_library]: if version == "latest": deps.append(f"{third_party_library}latest: {third_party_library}") else: v = Version(version) if v.is_prerelease: deps.append(f"{third_party_library}{version}: {third_party_library}=={version}") else: deps.append(f"{third_party_library}{version}: {third_party_library}~={version}.0") self._ini["testenv"]["deps"] = indent_join(deps) if self["tox_testenv_extras"]: self._ini["testenv"]["extras"] = self["tox_testenv_extras"] testenv_commands = ["python --version"] if self["enable_tests"]: testenv_commands.append( f"python -m pytest --cov={self['import_name']} -r aR {self['tests_dir']}/ {{posargs}}" ) # TODO: for tox-isolation # testenv_commands.append( # f"python -m pytest --cov={{envsitepackagesdir}}/{self['import_name']} -r aR {self['tests_dir']}/ {{posargs}}" # ) elif not self["stubs_package"]: testenv_commands.append("python -m importcheck {posargs}") self._ini["testenv"]["commands"] = indent_join(testenv_commands)
def typed(): """ Add a 'py.typed' file and the associated trove classifier. """ # 3rd party from domdf_python_tools.paths import PathPlus from domdf_python_tools.stringlist import StringList from natsort import natsorted # this package from repo_helper.configupdater2 import ConfigUpdater from repo_helper.core import RepoHelper from repo_helper.utils import indent_join, stage_changes rh = RepoHelper(PathPlus.cwd()) rh.load_settings() py_typed = rh.target_repo / rh.templates.globals["import_name"] / "py.typed" if not py_typed.is_file(): py_typed.touch() stage_changes(rh.target_repo, [py_typed]) setup_cfg = rh.target_repo / "setup.cfg" pyproject_file = rh.target_repo / "pyproject.toml" if setup_cfg.is_file() and not rh.templates.globals["use_whey"]: content = setup_cfg.read_text() config = ConfigUpdater() config.read_string(content) existing_classifiers = config["metadata"]["classifiers"] existing_classifiers_string = str(existing_classifiers) classifiers = set( map(str.strip, existing_classifiers.value.split('\n'))) classifiers.add("Typing :: Typed") new_classifiers_lines = StringList( indent_join(natsorted(classifiers)).expandtabs(4)) new_classifiers_lines[0] = "classifiers =" new_classifiers_lines.blankline(ensure_single=True) setup_cfg.write_clean( content.replace(existing_classifiers_string, str(new_classifiers_lines))) if pyproject_file.is_file() and rh.templates.globals["use_whey"]: pyproject_config = dom_toml.load(pyproject_file) if "whey" in pyproject_config.get("tool", {}): classifiers = set( pyproject_config["tool"]["whey"]["base-classifiers"]) classifiers.add("Typing :: Typed") pyproject_config["tool"]["whey"]["base-classifiers"] = natsorted( classifiers) dom_toml.dump(pyproject_config, pyproject_file, encoder=dom_toml.TomlEncoder)