From c554272fd116f4abf2fa231c0595d31946c86dc1 Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Thu, 19 Dec 2024 13:46:32 +0000 Subject: [PATCH 1/6] Working on optionality --- {{ cookiecutter.repo_name }}/Makefile | 56 +++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/{{ cookiecutter.repo_name }}/Makefile b/{{ cookiecutter.repo_name }}/Makefile index 230ae4cb..52b3a467 100644 --- a/{{ cookiecutter.repo_name }}/Makefile +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -14,14 +14,36 @@ export # Import config variables include .cookiecutter/config +# Environment type detection +ENVIRONMENT_TYPE ?= venv + +# Virtual environment configuration +# These can be overridden through environment variables or .envrc +# Example for pyenv-virtualenv users: +# export VENV_PATH=${PYENV_ROOT}/versions/${REPO_NAME} +# export VENV_CREATE_CMD="pyenv virtualenv ${PYTHON_VERSION} ${REPO_NAME}" +# export VENV_ACTIVATE_CMD="pyenv activate ${REPO_NAME}" +# export VENV_DEACTIVATE_CMD="pyenv deactivate" +VENV_PATH ?= .venv +VENV_CREATE_CMD ?= python -m venv ${VENV_PATH} +VENV_ACTIVATE_CMD ?= source ${VENV_PATH}/bin/activate +VENV_DEACTIVATE_CMD ?= deactivate +VENV_REMOVE_CMD ?= rm -rf ${VENV_PATH} + # 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/venv-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 a project: setup environment, install dependencies, and configure git hooks +install: +ifeq ($(ENVIRONMENT_TYPE),conda) + $(MAKE) conda-remove .cookiecutter/state/conda-create .cookiecutter/state/setup-git +else + $(MAKE) venv-remove .cookiecutter/state/venv-create .cookiecutter/state/setup-git +endif + @direnv reload # Reload to activate the environment .PHONY: check-bucket-path @@ -84,6 +106,25 @@ define err (echo "$1, check $@.log for more info" && exit 1) endef +.PHONY: venv-remove +## Remove the virtual environment cleanly +venv-remove: + rm -rf ${VENV_PATH} + rm -f .cookiecutter/state/venv-create* + @direnv reload + +.cookiecutter/state/venv-create: + @echo -n "Creating virtual environment and installing dependencies" + @( \ + $(VENV_CREATE_CMD) && \ + $(VENV_ACTIVATE_CMD) && \ + pip install -r requirements.txt && \ + pip install -e ".[dev]" \ + ) > $@.log 2>&1 \ + || $(call err,Python environment setup failed) + @touch $@ + @echo " DONE" + .cookiecutter/state/conda-create: @echo -n "Creating environment ${REPO_NAME} and installing all dependencies" @(conda env create -q -n ${REPO_NAME} -f environment.yaml\ @@ -96,10 +137,17 @@ endef .cookiecutter/state/setup-git: @echo -n "Installing and configuring git pre-commit hooks" +ifeq ($(ENVIRONMENT_TYPE),conda) @(eval "$$(conda shell.bash activate "${REPO_NAME}")"\ - &&pre-commit install --install-hooks)\ + && pre-commit install --install-hooks)\ + > $@.log 2>&1\ + || $(call err,Git pre-commit setup failed) +else + @($(VENV_ACTIVATE_CMD)\ + && pre-commit install --install-hooks)\ > $@.log 2>&1\ || $(call err,Git pre-commit setup failed) +endif @touch $@ @echo " DONE" From 6940695c66eb5909ee5a58782d179a7c6ff454fb Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Fri, 20 Dec 2024 12:28:31 +0000 Subject: [PATCH 2/6] Ideas for optionality --- cookiecutter.json | 12 +- .../.cookiecutter/config | 30 ++++ {{ cookiecutter.repo_name }}/Makefile | 130 +++++++----------- 3 files changed, 87 insertions(+), 85 deletions(-) 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/{{ cookiecutter.repo_name }}/.cookiecutter/config b/{{ cookiecutter.repo_name }}/.cookiecutter/config index 493629a7..db6de4c6 100644 --- a/{{ cookiecutter.repo_name }}/.cookiecutter/config +++ b/{{ cookiecutter.repo_name }}/.cookiecutter/config @@ -3,3 +3,33 @@ 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 +ENVIRONMENT_NAME=${REPO_NAME} +ENV_CREATE=conda env create -n ${ENVIRONMENT_NAME} -f environment.yaml +ENV_ACTIVATE=conda activate ${ENVIRONMENT_NAME} +ENV_DEACTIVATE=conda deactivate +ENV_REMOVE=conda env remove -n ${ENVIRONMENT_NAME} +ENV_UPDATE=conda env update -n ${ENVIRONMENT_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 +ENVIRONMENT_NAME=${REPO_NAME} +ENV_CREATE=pyenv virtualenv ${PYTHON_VERSION} ${ENVIRONMENT_NAME} +ENV_ACTIVATE=pyenv activate ${ENVIRONMENT_NAME} +ENV_DEACTIVATE=pyenv deactivate +ENV_REMOVE=pyenv virtualenv-delete ${ENVIRONMENT_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 52b3a467..040469f3 100644 --- a/{{ cookiecutter.repo_name }}/Makefile +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -14,37 +14,15 @@ export # Import config variables include .cookiecutter/config -# Environment type detection -ENVIRONMENT_TYPE ?= venv - -# Virtual environment configuration -# These can be overridden through environment variables or .envrc -# Example for pyenv-virtualenv users: -# export VENV_PATH=${PYENV_ROOT}/versions/${REPO_NAME} -# export VENV_CREATE_CMD="pyenv virtualenv ${PYTHON_VERSION} ${REPO_NAME}" -# export VENV_ACTIVATE_CMD="pyenv activate ${REPO_NAME}" -# export VENV_DEACTIVATE_CMD="pyenv deactivate" -VENV_PATH ?= .venv -VENV_CREATE_CMD ?= python -m venv ${VENV_PATH} -VENV_ACTIVATE_CMD ?= source ${VENV_PATH}/bin/activate -VENV_DEACTIVATE_CMD ?= deactivate -VENV_REMOVE_CMD ?= rm -rf ${VENV_PATH} - # 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/venv-create.log) +$(shell touch .cookiecutter/state/python-env-create.log) .PHONY: install -## Install a project: setup environment, install dependencies, and configure git hooks +## Install the project: setup environment, install dependencies, and configure git hooks install: -ifeq ($(ENVIRONMENT_TYPE),conda) - $(MAKE) conda-remove .cookiecutter/state/conda-create .cookiecutter/state/setup-git -else - $(MAKE) venv-remove .cookiecutter/state/venv-create .cookiecutter/state/setup-git -endif - @direnv reload # Reload to activate the environment - + $(MAKE) python-env-remove .cookiecutter/state/python-env-create .cookiecutter/state/setup-git + @direnv reload .PHONY: check-bucket-path check-bucket-path: @@ -72,24 +50,33 @@ 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 - $(MAKE) -s pip-install - direnv reload +.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-update +## Update the environment based on dependency files +python-env-update: + @echo "Updating ${ENVIRONMENT_TYPE} environment..." + @${ENV_UPDATE} + $(MAKE) -s pip-install + @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 + @pip install -e ".[dev]" .PHONY: clean ## Delete all compiled Python files @@ -106,50 +93,27 @@ define err (echo "$1, check $@.log for more info" && exit 1) endef -.PHONY: venv-remove -## Remove the virtual environment cleanly -venv-remove: - rm -rf ${VENV_PATH} - rm -f .cookiecutter/state/venv-create* - @direnv reload - -.cookiecutter/state/venv-create: - @echo -n "Creating virtual environment and installing dependencies" - @( \ - $(VENV_CREATE_CMD) && \ - $(VENV_ACTIVATE_CMD) && \ - pip install -r requirements.txt && \ - pip install -e ".[dev]" \ - ) > $@.log 2>&1 \ - || $(call err,Python environment setup failed) - @touch $@ - @echo " DONE" - -.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) - @touch $@ - @echo " DONE" +.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" -ifeq ($(ENVIRONMENT_TYPE),conda) - @(eval "$$(conda shell.bash activate "${REPO_NAME}")"\ - && pre-commit install --install-hooks)\ - > $@.log 2>&1\ - || $(call err,Git pre-commit setup failed) -else - @($(VENV_ACTIVATE_CMD)\ - && pre-commit install --install-hooks)\ - > $@.log 2>&1\ - || $(call err,Git pre-commit setup failed) -endif - @touch $@ - @echo " DONE" + @echo -n "Installing and configuring git pre-commit hooks" + @( \ + ${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" ################################################################################# From 351d442bc8fd513fd0d5061146e0197aca0fea67 Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Fri, 20 Dec 2024 15:22:01 +0000 Subject: [PATCH 3/6] Remove redundant ENVIRONMENT_NAME var --- .../.cookiecutter/config | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/{{ cookiecutter.repo_name }}/.cookiecutter/config b/{{ cookiecutter.repo_name }}/.cookiecutter/config index db6de4c6..9a7f39cf 100644 --- a/{{ cookiecutter.repo_name }}/.cookiecutter/config +++ b/{{ cookiecutter.repo_name }}/.cookiecutter/config @@ -7,12 +7,11 @@ export ENVIRONMENT_TYPE={{ cookiecutter.venv_type }} {% if cookiecutter.venv_type == 'conda' %} # Conda configuration -ENVIRONMENT_NAME=${REPO_NAME} -ENV_CREATE=conda env create -n ${ENVIRONMENT_NAME} -f environment.yaml -ENV_ACTIVATE=conda activate ${ENVIRONMENT_NAME} +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 ${ENVIRONMENT_NAME} -ENV_UPDATE=conda env update -n ${ENVIRONMENT_NAME} -f environment.yaml +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 @@ -25,11 +24,10 @@ ENV_UPDATE=pip install -r requirements.txt ENV_INSTALL_EXTRAS=true {% else %} # pyenv-virtualenv configuration -ENVIRONMENT_NAME=${REPO_NAME} -ENV_CREATE=pyenv virtualenv ${PYTHON_VERSION} ${ENVIRONMENT_NAME} -ENV_ACTIVATE=pyenv activate ${ENVIRONMENT_NAME} +ENV_CREATE=pyenv virtualenv ${PYTHON_VERSION} ${REPO_NAME} +ENV_ACTIVATE=pyenv activate ${REPO_NAME} ENV_DEACTIVATE=pyenv deactivate -ENV_REMOVE=pyenv virtualenv-delete ${ENVIRONMENT_NAME} +ENV_REMOVE=pyenv virtualenv-delete ${REPO_NAME} ENV_UPDATE=pip install -r requirements.txt ENV_INSTALL_EXTRAS=true {% endif %} From 2f2e75e951140fe5f2e728909c1fa5e28439c75c Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Fri, 20 Dec 2024 15:22:40 +0000 Subject: [PATCH 4/6] Fix indentation issue --- {{ cookiecutter.repo_name }}/Makefile | 64 +++++++++++++-------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/{{ cookiecutter.repo_name }}/Makefile b/{{ cookiecutter.repo_name }}/Makefile index 040469f3..d683a050 100644 --- a/{{ cookiecutter.repo_name }}/Makefile +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -21,8 +21,8 @@ $(shell touch .cookiecutter/state/python-env-create.log) .PHONY: install ## 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 + $(MAKE) python-env-remove .cookiecutter/state/python-env-create .cookiecutter/state/setup-git + @direnv reload .PHONY: check-bucket-path check-bucket-path: @@ -53,30 +53,30 @@ docs-open: .PHONY: python-env-create ## Create the virtual environment python-env-create: - @echo "Creating ${ENVIRONMENT_TYPE} environment..." - @${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 + @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-update ## Update the environment based on dependency files python-env-update: - @echo "Updating ${ENVIRONMENT_TYPE} environment..." - @${ENV_UPDATE} - $(MAKE) -s pip-install - @direnv reload + @echo "Updating ${ENVIRONMENT_TYPE} environment..." + @${ENV_UPDATE} + $(MAKE) -s pip-install + @direnv reload .PHONY: pip-install ## Install package in editable mode with dev dependencies pip-install: - @pip install -e ".[dev]" + @pip install -e ".[dev]" .PHONY: clean ## Delete all compiled Python files @@ -94,26 +94,26 @@ define err endef .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" + @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" - @( \ - ${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" + @echo -n "Installing and configuring git pre-commit hooks" + @( \ + ${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" ################################################################################# From 2f790438063ff75624d6cc52cb9b84a70d3dcffa Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Fri, 20 Dec 2024 15:25:44 +0000 Subject: [PATCH 5/6] Renaming tests --- tests/test_creation.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) 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]: From 3c745d3531bc7c04e44eb029387566ecae228f09 Mon Sep 17 00:00:00 2001 From: HarrisonWilde Date: Fri, 20 Dec 2024 15:43:58 +0000 Subject: [PATCH 6/6] Add python-env-activate function to make file --- {{ cookiecutter.repo_name }}/Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/{{ cookiecutter.repo_name }}/Makefile b/{{ cookiecutter.repo_name }}/Makefile index d683a050..510c39b4 100644 --- a/{{ cookiecutter.repo_name }}/Makefile +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -65,6 +65,12 @@ python-env-remove: @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: