diff --git a/cookiecutter.json b/cookiecutter.json index cc35ebb3..40b0b954 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -3,5 +3,13 @@ "repo_name": "{{ cookiecutter.project_name.lstrip('0123456789.- ').replace(' ', '_').replace('-', '_').lower() }}", "author_name": "Nesta", "description": "A short description of the project.", - "openness": ["public", "private"] -} + "openness": [ + "public", + "private" + ], + "venv_type": [ + "conda", + "venv", + "pyenv-virtualenv" + ] +} \ No newline at end of file diff --git a/tests/test_creation.py b/tests/test_creation.py index f6d7d786..351ca12d 100644 --- a/tests/test_creation.py +++ b/tests/test_creation.py @@ -150,29 +150,25 @@ def test_folders(self): assert len(set(abs_expected_dirs) ^ set(abs_dirs)) == 0 @pytest.mark.usefixtures("conda_env") - def test_conda(self): - """Test conda environment is created, modified, and destroyed.""" + def test_python_env(self): + """Test python environment is created, modified, and destroyed.""" with ch_dir(self.path): try: - p = shell(["make", ".cookiecutter/state/conda-create"]) + p = shell(["make", ".cookiecutter/state/python-env-create"]) assert " DONE" in p[-1] assert self.env_path.exists() # Add an extra pip dependency check_output(["echo", "nuts_finder", ">>", "requirements.txt"]) - p = shell(["make", "conda-update"]) - - # Add an extra conda dependency - check_output(["echo", " - tqdm", ">>", "environment.yaml"]) - p = shell(["make", "conda-update"]) + p = shell(["make", "python-env-update"]) except CalledProcessError: - log_path = Path(".cookiecutter/state/conda-create.log") + log_path = Path(".cookiecutter/state/python-env-create.log") if log_path.exists(): with log_path.open() as f: - print("conda-create.log:\n", f.read()) + print("python-env-create.log:\n", f.read()) raise finally: - p = shell(["make", "conda-remove"]) + p = shell(["make", "python-env-remove"]) def test_git(self): """Test expected git branches exist.""" @@ -201,16 +197,16 @@ def test_install(self): output = "".join(shell(["bash", "-c", "source .envrc && which python"])) print(output) - # Conda env activated by .envrc + # python env activated by .envrc assert f"{pytest.param.get('repo_name')}/bin/python" in output, output except CalledProcessError: - log_path = Path(".cookiecutter/state/conda-create.log") + log_path = Path(".cookiecutter/state/python-env-create.log") if log_path.exists(): with log_path.open() as f: - print("conda-create.log:\n", f.read()) + print("python-env-create.log:\n", f.read()) raise finally: - p = shell(["make", "conda-remove"]) + p = shell(["make", "python-env-remove"]) def shell(cmd: List[str]) -> List[str]: diff --git a/{{ cookiecutter.repo_name }}/.cookiecutter/config b/{{ cookiecutter.repo_name }}/.cookiecutter/config index 493629a7..9a7f39cf 100644 --- a/{{ cookiecutter.repo_name }}/.cookiecutter/config +++ b/{{ cookiecutter.repo_name }}/.cookiecutter/config @@ -3,3 +3,31 @@ export PROJECT_NAME={{ cookiecutter.project_name }} export REPO_NAME={{ cookiecutter.repo_name }} export DESCRIPTION={{ cookiecutter.description }} export PROJECT_OPENNESS={{ cookiecutter.openness }} +export ENVIRONMENT_TYPE={{ cookiecutter.venv_type }} + +{% if cookiecutter.venv_type == 'conda' %} +# Conda configuration +ENV_CREATE=conda env create -n ${REPO_NAME} -f environment.yaml +ENV_ACTIVATE=conda activate ${REPO_NAME} +ENV_DEACTIVATE=conda deactivate +ENV_REMOVE=conda env remove -n ${REPO_NAME} +ENV_UPDATE=conda env update -n ${REPO_NAME} -f environment.yaml +ENV_INSTALL_EXTRAS=conda install pip +{% elif cookiecutter.venv_type == 'venv' %} +# venv configuration +VENV_PATH=.venv +ENV_CREATE=python -m venv ${VENV_PATH} +ENV_ACTIVATE=source ${VENV_PATH}/bin/activate +ENV_DEACTIVATE=deactivate +ENV_REMOVE=rm -rf ${VENV_PATH} +ENV_UPDATE=pip install -r requirements.txt +ENV_INSTALL_EXTRAS=true +{% else %} +# pyenv-virtualenv configuration +ENV_CREATE=pyenv virtualenv ${PYTHON_VERSION} ${REPO_NAME} +ENV_ACTIVATE=pyenv activate ${REPO_NAME} +ENV_DEACTIVATE=pyenv deactivate +ENV_REMOVE=pyenv virtualenv-delete ${REPO_NAME} +ENV_UPDATE=pip install -r requirements.txt +ENV_INSTALL_EXTRAS=true +{% endif %} diff --git a/{{ cookiecutter.repo_name }}/Makefile b/{{ cookiecutter.repo_name }}/Makefile index 230ae4cb..510c39b4 100644 --- a/{{ cookiecutter.repo_name }}/Makefile +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -16,13 +16,13 @@ include .cookiecutter/config # Ensure directory to track and log setup state exists $(shell mkdir -p .cookiecutter/state) -$(shell touch .cookiecutter/state/conda-create.log) +$(shell touch .cookiecutter/state/python-env-create.log) .PHONY: install -## Install a project: remove existing conda env (if exists); create conda env; install local package; setup git hooks -install: conda-remove .cookiecutter/state/conda-create .cookiecutter/state/setup-git - @direnv reload # Now the conda env exists, reload to activate it - +## Install the project: setup environment, install dependencies, and configure git hooks +install: + $(MAKE) python-env-remove .cookiecutter/state/python-env-create .cookiecutter/state/setup-git + @direnv reload .PHONY: check-bucket-path check-bucket-path: @@ -50,25 +50,40 @@ docs-clean: docs-open: $(OPEN) docs/_build/index.html -.PHONY: conda-update -## Update the conda-environment based on changes to `environment.yaml` -conda-update: - conda env update -n ${REPO_NAME} -f environment.yaml +.PHONY: python-env-create +## Create the virtual environment +python-env-create: + @echo "Creating ${ENVIRONMENT_TYPE} environment..." + @${ENV_CREATE} + +.PHONY: python-env-remove +## Remove the virtual environment +python-env-remove: + @echo "Removing ${ENVIRONMENT_TYPE} environment..." + -@${ENV_DEACTIVATE} 2>/dev/null || true + -@${ENV_REMOVE} 2>/dev/null || true + @rm -f .cookiecutter/state/python-env-create* + @direnv reload + +.PHONY: python-env-activate +## Activate the virtual environment +python-env-activate: + @echo "Activating ${ENVIRONMENT_TYPE} environment..." + @${ENV_ACTIVATE} + +.PHONY: python-env-update +## Update the environment based on dependency files +python-env-update: + @echo "Updating ${ENVIRONMENT_TYPE} environment..." + @${ENV_UPDATE} $(MAKE) -s pip-install - direnv reload + @direnv reload .PHONY: pip-install -## Install our package and requirements in editable mode (including development dependencies) +## Install package in editable mode with dev dependencies pip-install: @pip install -e ".[dev]" -.PHONY: conda-remove -## Remove the conda-environment cleanly, using -f so works even if no environment to be removed -conda-remove: - conda env remove -n ${REPO_NAME} - rm -f .cookiecutter/state/conda-create* - @direnv reload - .PHONY: clean ## Delete all compiled Python files clean: @@ -84,22 +99,25 @@ define err (echo "$1, check $@.log for more info" && exit 1) endef -.cookiecutter/state/conda-create: - @echo -n "Creating environment ${REPO_NAME} and installing all dependencies" - @(conda env create -q -n ${REPO_NAME} -f environment.yaml\ - && eval "$$(conda shell.bash activate "${REPO_NAME}")"\ - && pip install -e ".[dev]")\ - > $@.log 2>&1\ - || $(call err,Python environment setup failed) +.cookiecutter/state/python-env-create: + @echo -n "Creating ${ENVIRONMENT_TYPE} environment and installing dependencies" + @( \ + ${ENV_CREATE} && \ + ${ENV_ACTIVATE} && \ + ${ENV_INSTALL_EXTRAS} && \ + pip install -e ".[dev]" \ + ) > $@.log 2>&1 \ + || (echo "Environment setup failed, check $@.log for details" && exit 1) @touch $@ @echo " DONE" .cookiecutter/state/setup-git: @echo -n "Installing and configuring git pre-commit hooks" - @(eval "$$(conda shell.bash activate "${REPO_NAME}")"\ - &&pre-commit install --install-hooks)\ - > $@.log 2>&1\ - || $(call err,Git pre-commit setup failed) + @( \ + ${ENV_ACTIVATE} && \ + pre-commit install --install-hooks \ + ) > $@.log 2>&1 \ + || (echo "Git pre-commit setup failed, check $@.log for details" && exit 1) @touch $@ @echo " DONE"