Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: set preferred Python version for dev environment #245

Merged
merged 11 commits into from
Dec 6, 2023
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"prereleased",
"prettierignore",
"prettierrc",
"pyenv",
"pyright",
"pyupgrade",
"redeboer",
Expand Down
1 change: 1 addition & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
tasks:
- init: pyenv local 3.8
- init: pip install -e .[dev]

github:
Expand Down
32 changes: 30 additions & 2 deletions src/repoma/check_dev_files/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from __future__ import annotations

import re
import sys
from argparse import ArgumentParser
from typing import Sequence
from typing import TYPE_CHECKING, Any, Sequence

from repoma.check_dev_files.deprecated import remove_deprecated_tools
from repoma.utilities.executor import Executor
Expand All @@ -13,6 +14,7 @@
black,
citation,
commitlint,
conda,
cspell,
editorconfig,
github_labels,
Expand All @@ -26,6 +28,7 @@
pyright,
pytest,
pyupgrade,
readthedocs,
release_drafter,
ruff,
setup_cfg,
Expand All @@ -34,6 +37,9 @@
vscode,
)

if TYPE_CHECKING:
from repoma.utilities.project_info import PythonVersion

Check warning on line 41 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L41

Added line #L41 was not covered by tests


def main(argv: Sequence[str] | None = None) -> int:
parser = _create_argparse()
Expand All @@ -42,9 +48,11 @@
if not args.repo_title:
args.repo_title = args.repo_name
has_notebooks = not args.no_notebooks
dev_python_version = __get_python_version(args.dev_python_version)

Check warning on line 51 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L51

Added line #L51 was not covered by tests
executor = Executor()
executor(citation.main)
executor(commitlint.main)
executor(conda.main, dev_python_version)

Check warning on line 55 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L55

Added line #L55 was not covered by tests
executor(cspell.main, args.no_cspell_update)
executor(editorconfig.main, args.no_python)
if not args.allow_labels:
Expand All @@ -54,6 +62,7 @@
github_workflows.main,
allow_deprecated=args.allow_deprecated_workflows,
doc_apt_packages=_to_list(args.doc_apt_packages),
python_version=dev_python_version,
no_macos=args.no_macos,
no_pypi=args.no_pypi,
no_version_branches=args.no_version_branches,
Expand Down Expand Up @@ -82,9 +91,10 @@
update_pip_constraints.main,
cron_frequency=args.pin_requirements,
)
executor(readthedocs.main, dev_python_version)

Check warning on line 94 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L94

Added line #L94 was not covered by tests
executor(remove_deprecated_tools, args.keep_issue_templates)
executor(vscode.main, has_notebooks)
executor(gitpod.main, args.no_gitpod)
executor(gitpod.main, args.no_gitpod, dev_python_version)

Check warning on line 97 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L97

Added line #L97 was not covered by tests
executor(precommit.main)
return executor.finalize(exception=False)

Expand Down Expand Up @@ -178,6 +188,13 @@
default=False,
help="Do not perform the check on labels.toml",
)
parser.add_argument(

Check warning on line 191 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L191

Added line #L191 was not covered by tests
"--dev-python-version",
default="3.8",
help="Specify the Python version for your developer environment",
required=False,
type=str,
)
parser.add_argument(
"--no-macos",
action="store_true",
Expand Down Expand Up @@ -263,5 +280,16 @@
return sorted(space_separated.split(" "))


def __get_python_version(arg: Any) -> PythonVersion:
if not isinstance(arg, str):
msg = f"--dev-python-version must be a string, not {type(arg).__name__}"
raise TypeError(msg)
arg = arg.strip()

Check warning on line 287 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L285-L287

Added lines #L285 - L287 were not covered by tests
if not re.match(r"^3\.\d+$", arg):
msg = f"Invalid Python version: {arg}"
raise ValueError(msg)
return arg # type: ignore[return-value]

Check warning on line 291 in src/repoma/check_dev_files/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/__init__.py#L289-L291

Added lines #L289 - L291 were not covered by tests


if __name__ == "__main__":
sys.exit(main())
73 changes: 73 additions & 0 deletions src/repoma/check_dev_files/conda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Update the :file:`environment.yml` Conda environment file."""

from __future__ import annotations

from typing import TYPE_CHECKING

from ruamel.yaml.scalarstring import PlainScalarString

from repoma.errors import PrecommitError
from repoma.utilities import CONFIG_PATH
from repoma.utilities.project_info import PythonVersion, get_constraints_file
from repoma.utilities.yaml import create_prettier_round_trip_yaml

if TYPE_CHECKING:
from ruamel.yaml.comments import CommentedMap, CommentedSeq

Check warning on line 15 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L15

Added line #L15 was not covered by tests


def main(python_version: PythonVersion) -> None:
if not CONFIG_PATH.conda.exists():
return
yaml = create_prettier_round_trip_yaml()
conda_env: CommentedMap = yaml.load(CONFIG_PATH.conda)
conda_deps: CommentedSeq = conda_env.get("dependencies", [])

Check warning on line 23 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L20-L23

Added lines #L20 - L23 were not covered by tests

updated = _update_python_version(python_version, conda_deps)
updated |= _update_pip_dependencies(python_version, conda_deps)

Check warning on line 26 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L25-L26

Added lines #L25 - L26 were not covered by tests
if updated:
yaml.dump(conda_env, CONFIG_PATH.conda)
msg = f"Set the Python version in {CONFIG_PATH.conda} to {python_version}"
raise PrecommitError(msg)

Check warning on line 30 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L28-L30

Added lines #L28 - L30 were not covered by tests


def _update_python_version(version: PythonVersion, conda_deps: CommentedSeq) -> bool:
idx = __find_python_dependency_index(conda_deps)
expected = f"python=={version}.*"

Check warning on line 35 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L34-L35

Added lines #L34 - L35 were not covered by tests
if idx is not None and conda_deps[idx] != expected:
conda_deps[idx] = expected
return True
return False

Check warning on line 39 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L37-L39

Added lines #L37 - L39 were not covered by tests


def _update_pip_dependencies(version: PythonVersion, conda_deps: CommentedSeq) -> bool:
pip_deps = __get_pip_dependencies(conda_deps)

Check warning on line 43 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L43

Added line #L43 was not covered by tests
if pip_deps is None:
return False
constraints_file = get_constraints_file(version)

Check warning on line 46 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L45-L46

Added lines #L45 - L46 were not covered by tests
if constraints_file is None:
expected_pip = "-e .[dev]"

Check warning on line 48 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L48

Added line #L48 was not covered by tests
else:
expected_pip = f"-c {constraints_file} -e .[dev]"

Check warning on line 50 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L50

Added line #L50 was not covered by tests
if len(pip_deps) and pip_deps[0] != expected_pip:
pip_deps[0] = PlainScalarString(expected_pip)
return True
return False

Check warning on line 54 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L52-L54

Added lines #L52 - L54 were not covered by tests


def __find_python_dependency_index(dependencies: CommentedSeq) -> int | None:
for i, dep in enumerate(dependencies):
if not isinstance(dep, str):
continue

Check warning on line 60 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L60

Added line #L60 was not covered by tests
if dep.strip().startswith("python"):
return i
return None

Check warning on line 63 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L62-L63

Added lines #L62 - L63 were not covered by tests


def __get_pip_dependencies(dependencies: CommentedSeq) -> CommentedSeq | None:
for dep in dependencies:
if not isinstance(dep, dict):
continue
pip_deps = dep.get("pip")

Check warning on line 70 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L69-L70

Added lines #L69 - L70 were not covered by tests
if pip_deps is not None and isinstance(pip_deps, list):
return pip_deps
return None

Check warning on line 73 in src/repoma/check_dev_files/conda.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/conda.py#L72-L73

Added lines #L72 - L73 were not covered by tests
31 changes: 22 additions & 9 deletions src/repoma/check_dev_files/github_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from repoma.utilities import CONFIG_PATH, REPOMA_DIR, hash_file, write
from repoma.utilities.executor import Executor
from repoma.utilities.precommit import PrecommitConfig
from repoma.utilities.project_info import get_pypi_name
from repoma.utilities.project_info import PythonVersion, get_pypi_name
from repoma.utilities.vscode import (
add_extension_recommendation,
remove_extension_recommendation,
Expand All @@ -34,6 +34,7 @@
no_macos: bool,
no_pypi: bool,
no_version_branches: bool,
python_version: PythonVersion,
single_threaded: bool,
skip_tests: list[str],
test_extras: list[str],
Expand All @@ -45,6 +46,7 @@
allow_deprecated,
doc_apt_packages,
no_macos,
python_version,
single_threaded,
skip_tests,
test_extras,
Expand All @@ -59,7 +61,7 @@
yaml = create_prettier_round_trip_yaml()
workflow_path = CONFIG_PATH.github_workflow_dir / "cd.yml"
expected_data = yaml.load(REPOMA_DIR / workflow_path)
if no_pypi or not os.path.exists(CONFIG_PATH.setup_cfg):
if no_pypi or not CONFIG_PATH.setup_cfg.exists():
del expected_data["jobs"]["pypi"]
if no_version_branches:
del expected_data["jobs"]["push"]
Expand Down Expand Up @@ -93,6 +95,7 @@
allow_deprecated: bool,
doc_apt_packages: list[str],
no_macos: bool,
python_version: PythonVersion,
single_threaded: bool,
skip_tests: list[str],
test_extras: list[str],
Expand All @@ -102,6 +105,7 @@
REPOMA_DIR / CONFIG_PATH.github_workflow_dir / "ci.yml",
doc_apt_packages,
no_macos,
python_version,
single_threaded,
skip_tests,
test_extras,
Expand Down Expand Up @@ -135,32 +139,41 @@
path: Path,
doc_apt_packages: list[str],
no_macos: bool,
python_version: PythonVersion,
single_threaded: bool,
skip_tests: list[str],
test_extras: list[str],
) -> tuple[YAML, dict]:
yaml = create_prettier_round_trip_yaml()
config = yaml.load(path)
__update_doc_section(config, doc_apt_packages)
__update_doc_section(config, doc_apt_packages, python_version)

Check warning on line 149 in src/repoma/check_dev_files/github_workflows.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/github_workflows.py#L149

Added line #L149 was not covered by tests
__update_pytest_section(config, no_macos, single_threaded, skip_tests, test_extras)
__update_style_section(config)
__update_style_section(config, python_version)

Check warning on line 151 in src/repoma/check_dev_files/github_workflows.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/github_workflows.py#L151

Added line #L151 was not covered by tests
return yaml, config


def __update_doc_section(config: CommentedMap, apt_packages: list[str]) -> None:
def __update_doc_section(
config: CommentedMap, apt_packages: list[str], python_version: PythonVersion
) -> None:
if not os.path.exists("docs/"):
del config["jobs"]["doc"]
else:
with_section = config["jobs"]["doc"]["with"]
if python_version != "3.8":
with_section["python-version"] = DoubleQuotedScalarString(python_version)

Check warning on line 163 in src/repoma/check_dev_files/github_workflows.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/github_workflows.py#L163

Added line #L163 was not covered by tests
if apt_packages:
with_section["apt-packages"] = " ".join(apt_packages)
if not os.path.exists(CONFIG_PATH.readthedocs):
if not CONFIG_PATH.readthedocs.exists():
with_section["gh-pages"] = True
__update_with_section(config, job_name="doc")


def __update_style_section(config: CommentedMap) -> None:
if not os.path.exists(CONFIG_PATH.precommit):
def __update_style_section(config: CommentedMap, python_version: PythonVersion) -> None:
if python_version != "3.8":
config["jobs"]["style"]["with"] = {

Check warning on line 173 in src/repoma/check_dev_files/github_workflows.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/github_workflows.py#L173

Added line #L173 was not covered by tests
"python-version": DoubleQuotedScalarString(python_version)
}
if not CONFIG_PATH.precommit.exists():
del config["jobs"]["style"]
else:
cfg = PrecommitConfig.load()
Expand All @@ -182,7 +195,7 @@
with_section = config["jobs"]["pytest"]["with"]
if test_extras:
with_section["additional-extras"] = ",".join(test_extras)
if os.path.exists(CONFIG_PATH.codecov):
if CONFIG_PATH.codecov.exists():
with_section["coverage-target"] = __get_package_name()
if not no_macos:
with_section["macos-python-version"] = DoubleQuotedScalarString("3.9")
Expand Down
23 changes: 13 additions & 10 deletions src/repoma/check_dev_files/gitpod.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@

from repoma.errors import PrecommitError
from repoma.utilities import CONFIG_PATH, REPOMA_DIR
from repoma.utilities.project_info import get_repo_url
from repoma.utilities.project_info import (
PythonVersion,
get_constraints_file,
get_repo_url,
)
from repoma.utilities.readme import add_badge
from repoma.utilities.yaml import write_yaml

__CONSTRAINTS_FILE = ".constraints/py3.8.txt"


def main(no_gitpod: bool) -> None:
def main(no_gitpod: bool, python_version: PythonVersion) -> None:
if no_gitpod:
if CONFIG_PATH.gitpod.exists():
os.remove(CONFIG_PATH.gitpod)
msg = f"Removed {CONFIG_PATH.gitpod} as requested by --no-gitpod"
raise PrecommitError(msg)
return
pin_dependencies = os.path.exists(__CONSTRAINTS_FILE)
error_message = ""
expected_config = _generate_gitpod_config(pin_dependencies)
expected_config = _generate_gitpod_config(python_version)

Check warning on line 27 in src/repoma/check_dev_files/gitpod.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/gitpod.py#L27

Added line #L27 was not covered by tests
if CONFIG_PATH.gitpod.exists():
with open(CONFIG_PATH.gitpod) as stream:
existing_config = yaml.load(stream, Loader=yaml.SafeLoader)
Expand Down Expand Up @@ -51,14 +52,16 @@
return {}


def _generate_gitpod_config(pin_dependencies: bool) -> dict:
def _generate_gitpod_config(python_version: PythonVersion) -> dict:
with open(REPOMA_DIR / ".template" / CONFIG_PATH.gitpod) as stream:
gitpod_config = yaml.load(stream, Loader=yaml.SafeLoader)
tasks = gitpod_config["tasks"]
if pin_dependencies:
tasks[0]["init"] = f"pip install -c {__CONSTRAINTS_FILE} -e .[dev]"
tasks[0]["init"] = f"pyenv local {python_version}"
constraints_file = get_constraints_file(python_version)
if constraints_file is None:
tasks[1]["init"] = "pip install -e .[dev]"
else:
tasks[0]["init"] = "pip install -e .[dev]"
tasks[1]["init"] = f"pip install -c {constraints_file} -e .[dev]"

Check warning on line 64 in src/repoma/check_dev_files/gitpod.py

View check run for this annotation

Codecov / codecov/patch

src/repoma/check_dev_files/gitpod.py#L64

Added line #L64 was not covered by tests
extensions = _extract_extensions()
if extensions:
gitpod_config["vscode"] = {"extensions": extensions}
Expand Down
Loading
Loading