def do_release(session: nox.Session) -> None: """Do a release to PyPI.""" # TODO: maybe add version validation if modified_files_in_git() or modified_files_in_git("--staged"): session.error( "Repository not clean, please remove, unstage, or commit your changes" ) if not len(session.posargs): session.error("Usage: nox -s publish -- <version> [publish-args]") else: version = session.posargs[0] update_version(session, version, MAIN_MODULE) update_changelog(session, version, CHANGELOG) commit_files(session, [MAIN_MODULE, CHANGELOG], message=f"Prepare for release {version}") create_git_tag(session, version, message=f"Release {version}") with isolated_temporary_checkout(session) as workdir: session.chdir(workdir) session.install("flit") session.run("flit", "publish", *session.posargs[1:]) session.chdir(THIS_DIR) update_version(session, next_development_version(version), MAIN_MODULE) update_changelog(session, "<unreleased>", CHANGELOG) commit_files(session, [MAIN_MODULE, CHANGELOG], message="Let's get back to development") session.log( "Alright, just do a push to GitHub and everything should be done") session.log("If you're paranoid, go ahead, verify that things are fine") session.log("If not, sit back and relax, you just did a release 🎉")
def update_constraints(session: nox.Session) -> None: """ Update the dependencies inplace. """ session.install("pip-tools") assert isinstance(session.python, str) python_version = session.python.replace(".", "") env = os.environ.copy() # CUSTOM_COMPILE_COMMAND is a pip-compile option that tells users how to # regenerate the constraints files env["CUSTOM_COMPILE_COMMAND"] = f"nox -s {session.name}" session.run( "pip-compile", "--allow-unsafe", "--upgrade", "cibuildwheel/resources/constraints.in", f"--output-file=cibuildwheel/resources/constraints-python{python_version}.txt", env=env, ) if session.python == PYTHON_ALL_VERSIONS[-1]: RESOURCES = DIR / "cibuildwheel" / "resources" shutil.copyfile( RESOURCES / f"constraints-python{python_version}.txt", RESOURCES / "constraints.txt", )
def check_versions(session: nox.Session) -> None: """Check that the version numbers declared for this project all match up.""" import httpx # Note: this can be linked to a specific hash by adding it between raw and {file.name} as another route segment. with httpx.Client() as client: requirements = client.get( "https://gist.githubusercontent.com/FasterSpeeding/139801789f00d15b4aa8ed2598fb524e/raw/requirements.json" ).json() # Note: this can be linked to a specific hash by adding it between raw and {file.name} as another route segment. code = client.get( "https://gist.githubusercontent.com/FasterSpeeding/139801789f00d15b4aa8ed2598fb524e/raw/check_versions.py" ).read() session.install(".", "--use-feature=in-tree-build") session.install(*requirements) # This is saved to a temporary file to avoid the source showing up in any of the output. # A try, finally is used to delete the file rather than relying on delete=True behaviour # as on Windows the file cannot be accessed by other processes if delete is True. file = tempfile.NamedTemporaryFile(delete=False) try: with file: file.write(code) required_version = _try_find_option(session, "--required-version", "-r") args = ["--required-version", required_version] if required_version else [] session.run("python", file.name, "-r", "sake", *args) finally: pathlib.Path(file.name).unlink(missing_ok=False)
def do_tests(session: nox.Session, *, coverage: bool) -> None: # Allow running test suite with a specific version of Black installed. # TODO: maybe consider dropping this and expect it to be done manually # using the environment created by setup-env. Although that would require # installing the test suite dependencies in that environment too, which # I am not too keen on :/ black_req = "" if "--black-req" in session.posargs: index = session.posargs.index("--black-req") black_req = session.posargs[index + 1] del session.posargs[index] del session.posargs[index] session.install("flit") # Use editable installs even without coverage because it's faster. session.run("flit", "install", _FLIT_EDITABLE, silent=True) session.install(black_req or "black") install_requirement(session, "tests-base") if coverage: install_requirement(session, "tests-cov") cmd = ["pytest"] if coverage: cmd.extend( ["--cov", "--cov-branch", "--cov-context", "test", "--cov-append"]) session.run(*cmd, *session.posargs)
def check_dependencies(session: nox.Session) -> None: """Verify that all the dependencies declared in pyproject.toml are up to date.""" import httpx # Note: this can be linked to a specific hash by adding it between raw and {file.name} as another route segment. with httpx.Client() as client: requirements = client.get( "https://gist.githubusercontent.com/FasterSpeeding/13e3d871f872fa09cf7bdc4144d62b2b/raw/requirements.json" ).json() # Note: this can be linked to a specific hash by adding it between raw and {file.name} as another route segment. code = client.get( "https://gist.githubusercontent.com/FasterSpeeding/13e3d871f872fa09cf7bdc4144d62b2b/raw/check_dependency.py" ).read() session.install(*requirements) # This is saved to a temporary file to avoid the source showing up in any of the output. # A try, finally is used to delete the file rather than relying on delete=True behaviour # as on Windows the file cannot be accessed by other processes if delete is True. file = tempfile.NamedTemporaryFile(delete=False) try: with file: file.write(code) session.run("python", file.name) finally: pathlib.Path(file.name).unlink(missing_ok=False)
def bump(session: nox.Session) -> None: """ Bump the major/minor/patch version (if nothing given, just shows the version). """ session.install("tomli") output = session.run( "python", "-c", "import tomli, pathlib; p = pathlib.Path('pyproject.toml'); print(tomli.loads(p.read_text())['project']['version'])", silent=True, ) current_version = output.strip() if not session.posargs: session.log(f"Current version: {current_version}") return new_version = session.posargs[0] session.log(f"Bumping from {current_version} to {new_version}") replace_version( Path("src/uhi/__init__.py"), '__version__ = "{version}"', current_version, new_version, ) replace_version(Path("pyproject.toml"), 'version = "{version}"', current_version, new_version) print(f"git switch -c chore/bump/{new_version}") print("git add -u src/uhi/__init__.py pyproject.toml") print(f"git commit -m 'chore: bump version to {new_version}'") print("gh pr create --fill")
def lint(session: nox.Session) -> None: """ Run the linter. """ session.install("pre-commit") session.run("pre-commit", "run", "--all-files", "--hook-stage=manual", *session.posargs)
def tests(session: nox.Session) -> str: """ Run the tests. """ wheel = build(session) session.install(f"./dist/{wheel}[test]") session.run("pytest", *session.posargs)
def black(session: nox.Session) -> None: """Run black, a code formatter and format checker.""" session.install(BLACK_PINNED_VERSION) if session.posargs: session.run("python", "-m", "black", *session.posargs) else: session.run("python", "-m", "black", *DEFAULT_LINT_ARGUMENTS)
def build(session: nox.Session) -> None: """ Build an SDist and wheel. """ session.install("build") session.run("python", "-m", "build")
def build_release(session: nox.Session) -> None: version = release.get_version_from_arguments(session) if not version: session.error("Usage: nox -s build-release -- YY.N[.P]") session.log("# Ensure no files in dist/") if release.have_files_in_folder("dist"): session.error( "There are files in dist/. Remove them and try again. " "You can use `git clean -fxdi -- dist` command to do this") session.log("# Install dependencies") session.install("setuptools", "wheel", "twine") with release.isolated_temporary_checkout(session, version) as build_dir: session.log( "# Start the build in an isolated, " f"temporary Git checkout at {build_dir!s}", ) with release.workdir(session, build_dir): tmp_dists = build_dists(session) tmp_dist_paths = (build_dir / p for p in tmp_dists) session.log(f"# Copying dists from {build_dir}") os.makedirs("dist", exist_ok=True) for dist, final in zip(tmp_dist_paths, tmp_dists): session.log(f"# Copying {dist} to {final}") shutil.copy(dist, final)
def tests_packaging(session: nox.Session) -> None: """ Run the packaging tests. """ session.install("-r", "tests/requirements.txt", "--prefer-binary") session.run("pytest", "tests/extra_python_package")
def upload_release(session: nox.Session) -> None: version = release.get_version_from_arguments(session) if not version: session.error("Usage: nox -s upload-release -- YY.N[.P]") session.log("# Install dependencies") session.install("twine") distribution_files = glob.glob("dist/*") session.log(f"# Distribution files: {distribution_files}") # Sanity check: Make sure there's 2 distribution files. count = len(distribution_files) if count != 2: session.error( f"Expected 2 distribution files for upload, got {count}. " f"Remove dist/ and run 'nox -s build-release -- {version}'") # Sanity check: Make sure the files are correctly named. distfile_names = (os.path.basename(fn) for fn in distribution_files) expected_distribution_files = [ f"pip-{version}-py3-none-any.whl", f"pip-{version}.tar.gz", ] if sorted(distfile_names) != sorted(expected_distribution_files): session.error( f"Distribution files do not seem to be for {version} release.") session.log("# Upload distributions") session.run("twine", "upload", *distribution_files)
def publish(session: Session) -> None: """ Publish a package to pypi """ session.install("poetry", "poetry-dynamic-versioning") session.run("poetry", "publish", "--build")
def dev(session: nox.Session) -> None: """Build a development environment and optionally run a command given as extra args""" session.install(*test_deps) session.install(*dev_deps) session.install("-e", ".") if session.posargs: session.run(*session.posargs, external=True)
def pylint(session: nox.Session) -> None: """ Run pylint. """ session.install("pylint", ".") session.run("pylint", "cibuildwheel", *session.posargs)
def check_manifest(session: nox.Session) -> None: """ Ensure all needed files are included in the manifest. """ session.install("check-manifest") session.run("check-manifest", *session.posargs)
def update(session: nox.Session) -> None: """ Get the latest (or given) version of cmake and update the copy with it. """ if session.posargs: (version, ) = session.posargs else: session.install("lastversion") version = session.run("lastversion", "kitware/cmake", log=False, silent=True).strip() cmake_url = f"https://raw.githubusercontent.com/Kitware/CMake/v{version}/Utilities/Sphinx/cmake.py" colors_url = f"https://raw.githubusercontent.com/Kitware/CMake/v{version}/Utilities/Sphinx/colors.py" urllib.request.urlretrieve(cmake_url, "sphinxcontrib/moderncmakedomain/cmake.py") urllib.request.urlretrieve(colors_url, "sphinxcontrib/moderncmakedomain/colors.py") session.install("bump2version") session.run("bumpversion", "--allow-dirty", "--new-version", version, "patch", "setup.cfg")
def check_wheel(session: nox.Session) -> None: """Check the wheel, should not be used directly.""" session.install("wheel", "twine") session.chdir(f"{session.cache_dir}/dist") session.run("twine", "check", "*", external=True) session.install(glob.glob("*.whl")[0]) session.run("python", "-m", "gcovr", "--help", external=True)
def tests_pipenv(session: nox.Session): """ Run test suite with pipenv sync. """ tmp_dir = session.create_tmp() for to_copy in (package_name, tests_name, pipfile_lock, notebooks_name): if Path(to_copy).is_dir(): copytree(to_copy, Path(tmp_dir) / to_copy) elif Path(to_copy).is_file(): copy2(to_copy, tmp_dir) elif Path(to_copy).exists(): ValueError("File not dir or file.") else: logging.error(f"Expected {to_copy} to exist.") session.chdir(tmp_dir) session.install("pipenv") session.run("pipenv", "--rm", success_codes=[0, 1]) session.run( "pipenv", "sync", "--python", f"{session.python}", "--dev", "--bare", ) session.run( "pipenv", "run", "pytest", )
def doctest(session: nox.Session) -> None: """Execute script `run_doctests.py` and measure code coverage. You can define arguments specific to the doctest session. The `doctest` session passes them to the `run_doctests.py` script. For example, to restrict testing to module `armatools.py` in Cython mode, write: nox -s doctest -- --file_doctests=armatools.py --python-mode=false Or shorter: nox -s doctest -- -f armatools.py -p f The `doctest` session only measures code coverage when no session-specific arguments are given, due to the mentioned restrictions inevitably resulting in incomplete code coverage measurements. """ _install_hydpy(session) session.install("coverage") session.chdir(_get_sitepackagepath(session)) session.run("python", "hydpy/docs/enable_autodoc.py") if not session.posargs: shutil.copy("hydpy/tests/hydpydoctestcustomize.py", "hydpydoctestcustomize.py") shutil.copy("hydpy/tests/hydpydoctestcustomize.pth", "hydpydoctestcustomize.pth") with _clean_environment(session): session.run("python", "hydpy/tests/run_doctests.py", *session.posargs) if not session.posargs: session.run("coverage", "combine") session.run("coverage", "report", "-m", "--skip-covered", "--fail-under=100")
def sphinx(session: nox.Session) -> None: """Build the HTML documentation and report warnings as errors. The `sphinx` session is more about building than testing. However, it also checks for completeness, substitutions' correctness, and things like that. Hence, we leave it here until we find a strong reason for moving it somewhere else. This session passes session-specific arguments to `sphinx-build`. For example, when building the official documentation on Travis-CI, we want to ensure that everything is correct, so we pass the `-W` flag to convert warnings into errors: nox -s sphinx -- -W """ _install_hydpy(session) session.install( "docutils", "sphinx", "sphinxcontrib-fulltoc", "sphinxprettysearchresults", "sphinxcontrib.bibtex<2", ) session.chdir(_get_sitepackagepath(session)) with _clean_environment(session): session.run("python", "hydpy/tests/run_doctests.py", "--python-mode=false") session.run("python", "hydpy/docs/enable_autodoc.py") session.run("python", "hydpy/docs/prepare.py") session.run("sphinx-build", "hydpy/docs/auto", "hydpy/docs/auto/build", *session.posargs) session.run("python", "hydpy/docs/modify_html.py")
def docs(session: nox.Session) -> None: session.install("-e", ".") session.install("-r", REQUIREMENTS["docs"]) def get_sphinx_build_command(kind: str) -> List[str]: # Having the conf.py in the docs/html is weird but needed because we # can not use a different configuration directory vs source directory # on RTD currently. So, we'll pass "-c docs/html" here. # See https://github.com/rtfd/readthedocs.org/issues/1543. # fmt: off return [ "sphinx-build", "-W", "-c", "docs/html", # see note above "-d", "docs/build/doctrees/" + kind, "-b", kind, "docs/" + kind, "docs/build/" + kind, ] # fmt: on session.run(*get_sphinx_build_command("html")) session.run(*get_sphinx_build_command("man"))
def mypy(session: nox.Session) -> None: """Run mypy.""" session.install("-r", "mypy-requirements.txt") session.run("mypy", "--version") session.run( "mypy", "dummyserver", "noxfile.py", "src/urllib3", "test/__init__.py", "test/with_dummyserver/test_connectionpool.py", "test/with_dummyserver/test_chunked_transfer.py", "test/with_dummyserver/test_https.py", "test/with_dummyserver/test_no_ssl.py", "test/with_dummyserver/test_poolmanager.py", "test/with_dummyserver/test_proxy_poolmanager.py", "test/conftest.py", "test/contrib/test_pyopenssl_dependencies.py", "test/contrib/test_socks.py", "test/port_helpers.py", "test/test_collections.py", "test/test_compatibility.py", "test/test_connection.py", "test/test_exceptions.py", "test/test_fields.py", "test/test_filepost.py", "test/test_no_ssl.py", "test/test_poolmanager.py", "test/test_proxymanager.py", "test/test_queue_monkeypatch.py", "test/test_retry.py", "test/test_ssl.py", "test/test_wait.py", "test/tz_stub.py", )
def dev_test_sim( session: nox.Session, sim: Optional[str], toplevel_lang: Optional[str], gpi_interface: Optional[str], ) -> None: """Test a development version of cocotb against a simulator.""" session.env["CFLAGS"] = "-Werror -Wno-deprecated-declarations -g --coverage" session.env["COCOTB_LIBRARY_COVERAGE"] = "1" session.env["CXXFLAGS"] = "-Werror" session.env["LDFLAGS"] = "--coverage" session.install(*test_deps, *coverage_deps) session.install("-e", ".") env = env_vars_for_test(sim, toplevel_lang, gpi_interface) config_str = stringify_dict(env) # Remove a potentially existing coverage file from a previous run for the # same test configuration. coverage_file = Path(f".coverage.test.sim-{sim}-{toplevel_lang}-{gpi_interface}") with suppress(FileNotFoundError): coverage_file.unlink() session.log(f"Running 'make test' against a simulator {config_str}") session.run("make", "test", external=True, env=env) session.log(f"Running simulator-specific tests against a simulator {config_str}") session.run( "pytest", "-v", "--cov=cocotb", "--cov-branch", # Don't display coverage report here "--cov-report=", "-k", "simulator_required", ) Path(".coverage").rename(".coverage.pytest") session.log(f"All tests passed with configuration {config_str}!") # Combine coverage produced during the test runs, and place it in a file # with a name specific to this invocation of dev_test_sim(). coverage_files = glob.glob("**/.coverage.cocotb", recursive=True) if not coverage_files: session.error( "No coverage files found. Something went wrong during the test execution." ) coverage_files.append(".coverage.pytest") session.run("coverage", "combine", "--append", *coverage_files) Path(".coverage").rename(coverage_file) session.log(f"Stored Python coverage for this test run in {coverage_file}.") # Combine coverage from all nox sessions as last step after all sessions # have completed. session.notify("dev_coverage_combine")
def build(session: nox.Session) -> None: """ Build SDists and wheels. """ session.install("build") session.run("python", "-m", "build") session.run("python", "-m", "build", env={"PYBIND11_GLOBAL_SDIST": "1"})
def docs(session: nox.Session) -> None: session.install("-r", "docs/requirements.txt") session.install(".[socks,secure,brotli]") session.chdir("docs") if os.path.exists("_build"): shutil.rmtree("_build") session.run("sphinx-build", "-b", "html", "-W", ".", "_build/html")
def tests(session: nox.Session) -> None: """ Run the unit and regular tests. """ shutil.rmtree("build", ignore_errors=True) session.install(".[test]") session.run("pytest", *session.posargs)
def conda_tests(session: nox.Session) -> None: """Run test suite with pytest.""" session.create_tmp() # Fixes permission errors on Windows session.conda_install( "--file", "requirements-conda-test.txt", "--channel", "conda-forge" ) session.install("-e", ".", "--no-deps") session.run("pytest", *session.posargs)
def pylint(session: nox.Session) -> None: """ Run pylint. """ session.install("pylint") session.install("-e", ".") session.run("pylint", "src", *session.posargs)