From 07d4bc63d224b00d1d536778dd7a2b669f9d7110 Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 19:52:55 +1100 Subject: [PATCH 01/10] Fix command to clean old API docs --- {{cookiecutter.package_name}}/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.package_name}}/pyproject.toml b/{{cookiecutter.package_name}}/pyproject.toml index 8b9ac9f..7b1867f 100644 --- a/{{cookiecutter.package_name}}/pyproject.toml +++ b/{{cookiecutter.package_name}}/pyproject.toml @@ -113,7 +113,7 @@ extra-dependencies = [ build = [ """ rm -rf \ - docs/source/api/sitemap_generator*.rst \ + docs/source/api/{{cookiecutter.package_name}}*.rst \ docs/source/api/modules.rst \ docs/build/* """, From 1395f2b23cc5ab916396a7edf4ac8cf4dd154b54 Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 19:57:46 +1100 Subject: [PATCH 02/10] Avoid usage of `rm` command in pyproject.toml --- {{cookiecutter.package_name}}/pyproject.toml | 7 +------ .../scripts/cleanup_docs.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 {{cookiecutter.package_name}}/scripts/cleanup_docs.py diff --git a/{{cookiecutter.package_name}}/pyproject.toml b/{{cookiecutter.package_name}}/pyproject.toml index 7b1867f..14a4dcc 100644 --- a/{{cookiecutter.package_name}}/pyproject.toml +++ b/{{cookiecutter.package_name}}/pyproject.toml @@ -111,12 +111,7 @@ extra-dependencies = [ [tool.hatch.envs.docs.scripts] build = [ - """ - rm -rf \ - docs/source/api/{{cookiecutter.package_name}}*.rst \ - docs/source/api/modules.rst \ - docs/build/* - """, + "python scripts/cleanup_docs.py", "sphinx-build -M html docs/source docs/build", ] build-dummy = [ diff --git a/{{cookiecutter.package_name}}/scripts/cleanup_docs.py b/{{cookiecutter.package_name}}/scripts/cleanup_docs.py new file mode 100644 index 0000000..b22ef02 --- /dev/null +++ b/{{cookiecutter.package_name}}/scripts/cleanup_docs.py @@ -0,0 +1,18 @@ +"""Remove docs build files and generated API docs""" + +import shutil +from pathlib import Path + +paths = [ + "docs/build", + "docs/source/api/modules.rst", + "docs/source/api/{{cookiecutter.package_name}}*.rst", +] + +for path in paths: + p = Path(path) + if p.exists(): + if p.is_dir(): + shutil.rmtree(p) + else: + p.unlink() From 497557715d3bba4a44d256562f791336c37eea81 Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 19:59:53 +1100 Subject: [PATCH 03/10] Have test_examples.py use cmd on Windows, else sh --- .../tests/test_examples.py | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/{{cookiecutter.package_name}}/tests/test_examples.py b/{{cookiecutter.package_name}}/tests/test_examples.py index 549166d..829f521 100644 --- a/{{cookiecutter.package_name}}/tests/test_examples.py +++ b/{{cookiecutter.package_name}}/tests/test_examples.py @@ -39,15 +39,22 @@ def run_in_venv( original_cwd = os.getcwd() script_dir = os.path.join(REPO_DIR, os.path.dirname(filepath)) filename = os.path.basename(filepath) - args = shlex.split( - ( - f'/bin/bash -c "source {VENV_DIR}/bin/activate ' - f'&& python {filename}"' + + if os.name == "nt": + # Windows + activate_cmd = f"{VENV_DIR}\\Scripts\\activate" + args = shlex.split( + f'cmd.exe /c "{activate_cmd} && python {filename}"' + ) + else: + # Unix-like systems + activate_cmd = f"source {VENV_DIR}/bin/activate" + args = shlex.split( + f'sh -c "{activate_cmd} && python {filename}"' ) - ) - env = {} - if os.environ["PATH"]: + env: dict[str, str] = {} + if os.environ.get("PATH"): env["PATH"] = os.environ["PATH"] if "LD_LIBRARY_PATH" in os.environ: env["LD_LIBRARY_PATH"] = os.environ["LD_LIBRARY_PATH"] @@ -55,26 +62,23 @@ def run_in_venv( if popen_kwargs is None: popen_kwargs = {} - popen_default_kwargs = { + popen_default_kwargs: dict[str, Any] = { "env": env, + "cwd": script_dir, + "timeout": timeout, "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, - "shell": False, } + popen_default_kwargs.update(popen_kwargs) try: - os.chdir(script_dir) - with subprocess.Popen( - args=args, - **{**popen_default_kwargs, **popen_kwargs}, - ) as proc: - _out, _err = proc.communicate(timeout=timeout) - returncode = proc.returncode + subprocess.run(args, **popen_default_kwargs, check=True) + return True + except subprocess.CalledProcessError: + return False finally: os.chdir(original_cwd) - return returncode == 0 - def test_quickstart_example(self) -> None: """check quickstart example""" assert ( From 3ac32c8a8ea4fc3ab3c02e5d17c25a3e853142dc Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 20:07:08 +1100 Subject: [PATCH 04/10] Ensure all Makefile targets work under Windows --- {{cookiecutter.package_name}}/Makefile | 32 ++++++++++-- .../scripts/generate_help.ps1 | 11 ++++ .../tests/test-dist.ps1 | 51 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 {{cookiecutter.package_name}}/scripts/generate_help.ps1 create mode 100644 {{cookiecutter.package_name}}/tests/test-dist.ps1 diff --git a/{{cookiecutter.package_name}}/Makefile b/{{cookiecutter.package_name}}/Makefile index 781aece..6f26179 100644 --- a/{{cookiecutter.package_name}}/Makefile +++ b/{{cookiecutter.package_name}}/Makefile @@ -1,9 +1,22 @@ # This makefile has been created to help developers perform common actions. -# Most actions assume it is operating in a virtual environment where the -# python command links to the appropriate virtual environment Python. MAKEFLAGS += --no-print-directory -GIT := $(shell command -v git) +ifeq ($(OS),Windows_NT) + UNAME_S := Windows +else + UNAME_S := $(shell uname -s) +endif + +# Set GIT variable based on the operating system +ifeq ($(UNAME_S), Linux) + GIT := $(shell command -v git) +endif +ifeq ($(UNAME_S), Darwin) + GIT := $(shell command -v git) +endif +ifeq ($(UNAME_S), Windows) + GIT := $(shell where git) +endif define CHECK_GIT @@ -21,11 +34,16 @@ endef # help: help - display makefile help information .PHONY: help +ifeq ($(UNAME_S), Windows) +help: + @powershell -File scripts/generate_help.ps1 -MakefilePath Makefile +else help: @grep "^#\shelp:" Makefile | \ grep -v grep | \ sed 's/\# help\: //' | \ sed 's/\# help\://' +endif # help: venv - enter a dev virtual environment @@ -143,10 +161,18 @@ dist: # help: dist-test - test a wheel distribution package .PHONY: dist-test +ifeq ($(UNAME_S), Windows) +dist-test: dist + @cd dist && \ + @powershell \ + -File ../tests/test-dist.ps1 \ + ./{{cookiecutter.package_name}}-*-py3-none-any.whl +else dist-test: dist @cd dist && \ ../tests/test-dist.bash \ ./{{cookiecutter.package_name}}-*-py3-none-any.whl +endif # help: dist-upload - upload a wheel distribution package diff --git a/{{cookiecutter.package_name}}/scripts/generate_help.ps1 b/{{cookiecutter.package_name}}/scripts/generate_help.ps1 new file mode 100644 index 0000000..6d69fec --- /dev/null +++ b/{{cookiecutter.package_name}}/scripts/generate_help.ps1 @@ -0,0 +1,11 @@ +param ( + [string]$MakefilePath = "Makefile" +) + +if (Test-Path $MakefilePath) { + Get-Content $MakefilePath | Select-String '^#\shelp:' | ForEach-Object { + $_.Line -replace '# help: ', '' -replace '# help:', '' + } +} else { + Write-Error "Makefile not found at path: $MakefilePath" +} diff --git a/{{cookiecutter.package_name}}/tests/test-dist.ps1 b/{{cookiecutter.package_name}}/tests/test-dist.ps1 new file mode 100644 index 0000000..660bc1f --- /dev/null +++ b/{{cookiecutter.package_name}}/tests/test-dist.ps1 @@ -0,0 +1,51 @@ +param ( + [string]$ReleaseArchivePattern = "./package_name-*-py3-none-any.whl" +) + +if (-not $ReleaseArchivePattern) { + Write-Host "usage: .\test-dist.ps1 package_name-YY.MM.MICRO-py3-none-any.whl" + exit 1 +} + +# Set ErrorActionPreference to Stop to treat all errors as terminating +$ErrorActionPreference = "Stop" + +try { + $ReleaseArchive = Get-ChildItem -Path $ReleaseArchivePattern | Select-Object -First 1 + + if (-not $ReleaseArchive) { + Write-Host "No matching release archive found for pattern: $ReleaseArchivePattern" + exit 1 + } + + Write-Host "Release archive: $ReleaseArchive" + + Write-Host "Removing any old artefacts" + if (Test-Path -Path test_venv) { + Remove-Item -Recurse -Force test_venv + } + + Write-Host "Creating test virtual environment" + python -m venv test_venv + + Write-Host "Entering test virtual environment" + & .\test_venv\Scripts\Activate.ps1 + + Write-Host "Upgrading pip" + pip install --upgrade pip + + Write-Host "Installing $ReleaseArchive" + pip install $ReleaseArchive.FullName + + Write-Host "Running tests" + python -m unittest discover -s .. + + Write-Host "Exiting test virtual environment" + deactivate + + Write-Host "Removing test virtual environment" + Remove-Item -Recurse -Force test_venv +} catch { + Write-Host "An error occurred: $_" + exit 1 +} From a989cda47cd2e89068a67c6ab1feef13e2cef947 Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 20:40:24 +1100 Subject: [PATCH 05/10] Have README.rst describe dependency installations --- README.rst | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.rst b/README.rst index 219377e..80e6a8c 100644 --- a/README.rst +++ b/README.rst @@ -78,10 +78,61 @@ cookiecutter using ``pip``. The example below shows how to do this. .. code-block:: console $ python -m venv --prompt cc ccvenv + $ $ source ccvenv/bin/activate + $ # or for cmd.exe: + $ # ccvenv\Scripts\activate.bat + $ # or for PowerShell: + $ # ccvenv\Scripts\Activate.ps1 + $ (cc) $ pip install -U pip # update pip to avoid any warnings (cc) $ pip install cookiecutter +If you do not yet have Hatch installed, now would be a good time to do +so. Refer to the installation instructions for your operating system +`here `_. + +It may also be a good idea to ensure you have ``git`` installed (and +it may be required for cookiecutter to function if using it to clone +this template). Under Windows, you can use `winget +`_. + +.. code-block:: console + + (cc) $ winget install --id Git.Git --exact --source winget + +Under macOS you can use `brew `_. + +.. code-block:: console + + (cc) $ brew install git + +Users of other operating systems likely already have it installed or +will be able to install it via their operating system's package +manager. + +If you wish to use the fancy Makefile included in this project, which +is entirely optional, you may wish to install the ``make`` +command. Under Windows, again using winget: + +.. code-block:: console + + (cc) $ winget install --id GnuWin32.Make --exact --source winget + +Unlike with git, you will need to `manually add +`_ the directory +containing ``make.exe`` to your PATH, which is typically something like: +``C:\Program Files(x86)\GnuWin32\bin\``. + +Under macOS you can again use brew. + +.. code-block:: console + + (cc) $ brew install make + +Users of other operating systems should again have no trouble finding +it in their operating system's package manager. + You are now ready to create a new Python project from the Cookiecutter template provided by this project. From a163dc08ab66f1278a13606eaa10ac11642fcc6e Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 20:43:37 +1100 Subject: [PATCH 06/10] Use double-quotes in `git commit -m ...` command Improves compatibility for those on Windows. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 80e6a8c..59c0f75 100644 --- a/README.rst +++ b/README.rst @@ -249,7 +249,7 @@ new repository at this point. $ git init $ git add . - $ git commit -m 'Initial cookiecutter-python-project setup' + $ git commit -m "Initial cookiecutter-python-project setup" With that out of the way, it will be easy to use git to undo any potential mistakes made while experimenting. From 7d903cab618d26da7d0f204cd87e3ad2984e2a0c Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 21:20:23 +1100 Subject: [PATCH 07/10] Add documentation on Windows Execution Policies --- README.rst | 65 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 59c0f75..b1db8c9 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,4 @@ +########################### Cookiecutter Python Project ########################### @@ -64,12 +65,18 @@ It is assumed that the new Python package will eventually be: The generated docs have some references and links to those sites. +=============== Getting Started =============== +-------------------- One Time Setup Steps -------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Install cookiecutter via pip +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + The process for using Cookiecutter to create a new Python package project starts with installing Cookiecutter. This is best done by creating a new virtual environment specifically for cookiecutter and then installing @@ -88,10 +95,20 @@ cookiecutter using ``pip``. The example below shows how to do this. (cc) $ pip install -U pip # update pip to avoid any warnings (cc) $ pip install cookiecutter + +^^^^^^^^^^^^^ +Install hatch +^^^^^^^^^^^^^ + If you do not yet have Hatch installed, now would be a good time to do so. Refer to the installation instructions for your operating system `here `_. + +^^^^^^^^^^^^^^^^^^^^^^ +Install git (optional) +^^^^^^^^^^^^^^^^^^^^^^ + It may also be a good idea to ensure you have ``git`` installed (and it may be required for cookiecutter to function if using it to clone this template). Under Windows, you can use `winget @@ -111,9 +128,14 @@ Users of other operating systems likely already have it installed or will be able to install it via their operating system's package manager. -If you wish to use the fancy Makefile included in this project, which -is entirely optional, you may wish to install the ``make`` -command. Under Windows, again using winget: + +^^^^^^^^^^^^^^^^^^^^^^^ +Install make (optional) +^^^^^^^^^^^^^^^^^^^^^^^ + +If you wish to use the fancy Makefile included in this project, you +may wish to install the ``make`` command. Under Windows, again using +winget: .. code-block:: console @@ -133,10 +155,31 @@ Under macOS you can again use brew. Users of other operating systems should again have no trouble finding it in their operating system's package manager. -You are now ready to create a new Python project from the Cookiecutter -template provided by this project. +.. code-block:: console +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +An important note for Windows users running make +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you are using the Makefile system, be aware that two of the targets +(``help`` and ``dist-test``) make use of PowerShell scripts to achieve +Windows compatibility. These may not run unless you adjust an +execution policy to permit them. This can be done by opening a Windows +PowerShell as an administrator (just right-click the launcher and +select ``Run as Administrator``) and issuing the following command: + +.. code-block:: console + + PS > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +You can read more about this `here +`_. + +Finally, you are ready to create a new Python project from the +Cookiecutter template provided by this project. + +-------------------- Create a new project -------------------- @@ -145,15 +188,16 @@ simply navigate to a directory where you want to create the new project, then run the ``cookiecutter`` command with a command line argument referencing this template. -The easiest method is to reference this template via its GitHub URL (where 'gh' -is a shortened form for GitHub): +The easiest method (which will fail if ``git`` is not installed) is to +reference this template via its GitHub URL (where 'gh' is a shortened +form for GitHub): .. code-block:: console (cc) $ cookiecutter gh:boltronics/cookiecutter-python-project -Alternatively, if you have cloned a local copy of this template you can -reference it directly: +Alternatively, if you have cloned or downloaded a local copy of this +template, you can reference it directly: .. code-block:: console @@ -172,6 +216,7 @@ cookiecutter virtual environment as it is no longer required. $ +-------------------- Manual Modifications -------------------- @@ -192,6 +237,7 @@ using the new project. `_. +======= Example ======= @@ -435,6 +481,7 @@ Here is an example of one in action: $ +===================================== Suggestions? Contributions? Problems? ===================================== From 2856d2f9467fa857f53652fba50819924bd4a390 Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 21:42:07 +1100 Subject: [PATCH 08/10] Adjust code-block source identifiers This prevents GitHub from showing a copy button, which unfortunately causes it to copy the prompt in addition to the commands. --- README.rst | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index b1db8c9..1d08ba6 100644 --- a/README.rst +++ b/README.rst @@ -82,7 +82,7 @@ starts with installing Cookiecutter. This is best done by creating a new virtual environment specifically for cookiecutter and then installing cookiecutter using ``pip``. The example below shows how to do this. -.. code-block:: console +.. code-block:: shell-session $ python -m venv --prompt cc ccvenv $ @@ -114,13 +114,13 @@ it may be required for cookiecutter to function if using it to clone this template). Under Windows, you can use `winget `_. -.. code-block:: console +.. code-block:: shell-session (cc) $ winget install --id Git.Git --exact --source winget Under macOS you can use `brew `_. -.. code-block:: console +.. code-block:: shell-session (cc) $ brew install git @@ -137,7 +137,7 @@ If you wish to use the fancy Makefile included in this project, you may wish to install the ``make`` command. Under Windows, again using winget: -.. code-block:: console +.. code-block:: shell-session (cc) $ winget install --id GnuWin32.Make --exact --source winget @@ -148,7 +148,7 @@ containing ``make.exe`` to your PATH, which is typically something like: Under macOS you can again use brew. -.. code-block:: console +.. code-block:: shell-session (cc) $ brew install make @@ -168,7 +168,7 @@ execution policy to permit them. This can be done by opening a Windows PowerShell as an administrator (just right-click the launcher and select ``Run as Administrator``) and issuing the following command: -.. code-block:: console +.. code-block:: shell-session PS > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser @@ -192,14 +192,14 @@ The easiest method (which will fail if ``git`` is not installed) is to reference this template via its GitHub URL (where 'gh' is a shortened form for GitHub): -.. code-block:: console +.. code-block:: shell-session (cc) $ cookiecutter gh:boltronics/cookiecutter-python-project Alternatively, if you have cloned or downloaded a local copy of this template, you can reference it directly: -.. code-block:: console +.. code-block:: shell-session (cc) $ cookiecutter path/to/cookiecutter-python-project @@ -210,7 +210,7 @@ shown in order. Once you have generated your new Python package project you can exit the cookiecutter virtual environment as it is no longer required. -.. code-block:: console +.. code-block:: shell-session (cc) $ deactivate $ @@ -258,7 +258,7 @@ and hyphens in it. The package display name is first converted to lowercase text and then any spaces or hyphens are converted to underscores to produce a Python package name. -.. code-block:: console +.. code-block:: shell-session (cc) $ cookiecutter gh:boltronics/cookiecutter-python-project [1/10] package_display_name (Package-Name): abc 123 @@ -284,14 +284,14 @@ Python package name. The project has been created in the ``abc_123`` directory. -.. code-block:: console +.. code-block:: shell-session $ cd abc_123 If you are planning to use git, it might be a good idea to create a new repository at this point. -.. code-block:: console +.. code-block:: shell-session $ git init $ git add . @@ -307,7 +307,7 @@ First, let's enter a project-specific virtual environment. Hatch will install any of the project's dependencies (if added to pyproject.toml) as well as the project itself as an editable package. -.. code-block:: console +.. code-block:: shell-session $ hatch shell (abc_123) $ @@ -321,7 +321,7 @@ There are a number of other virtual environments available to you, and most of these have their own packages and scripts to ease development. You can bring up a summary like so: -.. code-block:: console +.. code-block:: shell-session $ hatch env show Standalone @@ -358,7 +358,7 @@ development. You can bring up a summary like so: You can enter use these virtual environments like so: -.. code-block:: console +.. code-block:: shell-session $ hatch shell types (types) $ pip freeze @@ -391,7 +391,7 @@ If you have make installed, the included Makefile provides handy shortcuts for various Hatch commands and the configured scripts. You can print a summary of options via the `make help` command, like so: -.. code-block:: console +.. code-block:: shell-session $ make help @@ -420,10 +420,12 @@ can print a summary of options via the `make help` command, like so: dist-test - test a wheel distribution package dist-upload - upload a wheel distribution package + $ + Here is an example of one in action: -.. code-block:: console +.. code-block:: shell-session $ make test-verbose ────────────────────────────── hatch-test.py3.13 ─────────────────────────────── From 8bc47fc60d0e02a2af1abdb9e67dc473315c7d3e Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 22:21:06 +1100 Subject: [PATCH 09/10] Log error information in test_examples.py --- {{cookiecutter.package_name}}/tests/test_examples.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.package_name}}/tests/test_examples.py b/{{cookiecutter.package_name}}/tests/test_examples.py index 829f521..2110181 100644 --- a/{{cookiecutter.package_name}}/tests/test_examples.py +++ b/{{cookiecutter.package_name}}/tests/test_examples.py @@ -3,6 +3,7 @@ suite. """ +import logging import os import shlex import subprocess @@ -74,7 +75,12 @@ def run_in_venv( try: subprocess.run(args, **popen_default_kwargs, check=True) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as error: + logging.error("Error: %s", error) + if error.stdout: + logging.error("stdout: %s", error.stdout.decode()) + if error.stderr: + logging.error("stderr: %s", error.stderr.decode()) return False finally: os.chdir(original_cwd) From 382f43687ddeb1e097b6df7708b5cecd869fa12d Mon Sep 17 00:00:00 2001 From: Adam Bolte Date: Thu, 23 Jan 2025 22:24:28 +1100 Subject: [PATCH 10/10] Replace `source` with `.` for POSIX-compatibility --- {{cookiecutter.package_name}}/tests/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.package_name}}/tests/test_examples.py b/{{cookiecutter.package_name}}/tests/test_examples.py index 2110181..fa28ab8 100644 --- a/{{cookiecutter.package_name}}/tests/test_examples.py +++ b/{{cookiecutter.package_name}}/tests/test_examples.py @@ -49,7 +49,7 @@ def run_in_venv( ) else: # Unix-like systems - activate_cmd = f"source {VENV_DIR}/bin/activate" + activate_cmd = f". {VENV_DIR}/bin/activate" args = shlex.split( f'sh -c "{activate_cmd} && python {filename}"' )