Skip to content

Commit bdaa7a8

Browse files
committed
Copier
1 parent 8490669 commit bdaa7a8

40 files changed

+882
-588
lines changed

.copier-answers.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier
2-
_commit: v0.0.44
2+
_commit: v0.0.49
33
_src_path: gh:LabAutomationAndScreening/copier-base-template.git
44
description: Copier template for creating Python libraries and executables
55
python_ci_versions:

.devcontainer/Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
FROM mcr.microsoft.com/vscode/devcontainers/universal:2.9.0-focal
1+
# base image tags available at https://mcr.microsoft.com/v2/devcontainers/universal/tags/list
2+
# added the platform flag to override any local settings since this image is only compatible with linux/amd64. since this image is only x64 compatible, suppressing the hadolint rule
3+
# hadolint ignore=DL3029
4+
FROM --platform=linux/amd64 mcr.microsoft.com/devcontainers/universal:2.13.1-focal
25

36
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
47

.devcontainer/devcontainer.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
"service": "devcontainer",
44
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
55
"features": {
6-
"ghcr.io/devcontainers/features/aws-cli:1": {
6+
"ghcr.io/devcontainers/features/aws-cli:1.1.1": {
7+
// https://github.com/devcontainers/features/blob/main/src/aws-cli/devcontainer-feature.json
78
// view latest version https://raw.githubusercontent.com/aws/aws-cli/v2/CHANGELOG.rst
8-
"version": "2.24.24"
9+
"version": "2.27.14"
910
},
10-
"ghcr.io/devcontainers/features/python:1": {
11-
// https://github.com/devcontainers/features/tree/main/src/python
11+
"ghcr.io/devcontainers/features/python:1.7.1": {
12+
// https://github.com/devcontainers/features/blob/main/src/python/devcontainer-feature.json
1213
"version": "3.12.7",
1314
"installTools": false,
1415
"optimize": true
@@ -20,16 +21,16 @@
2021
"extensions": [
2122
// basic tooling
2223
"eamodio.gitlens@15.5.1",
23-
"ms-vscode.live-server@0.5.2024091601",
24+
"ms-vscode.live-server@0.5.2025051301",
2425
"MS-vsliveshare.vsliveshare@1.0.5905",
25-
"github.copilot@1.304.1523",
26-
"github.copilot-chat@0.27.2025042301",
26+
"github.copilot@1.320.1564",
27+
"github.copilot-chat@0.28.2025051402",
2728

2829
// Python
29-
"ms-python.python@2024.14.1",
30-
"ms-python.vscode-pylance@2024.9.2",
31-
"ms-vscode-remote.remote-containers@0.383.0",
32-
"charliermarsh.ruff@2024.54.0",
30+
"ms-python.python@2025.7.2025051401",
31+
"ms-python.vscode-pylance@2025.4.104",
32+
"ms-vscode-remote.remote-containers@0.414.0",
33+
"charliermarsh.ruff@2025.22.0",
3334

3435
// Misc file formats
3536
"bierner.markdown-mermaid@1.28.0",
@@ -42,6 +43,7 @@
4243
"editor.accessibilitySupport": "off", // turn off sounds
4344
"extensions.autoUpdate": false,
4445
"extensions.autoCheckUpdates": false,
46+
"livePreview.portNumber": 3025, // arbitrary not to conflict with default 3000 Nuxt port number
4547
"[python]": {
4648
"editor.formatOnSave": true,
4749
"editor.defaultFormatter": "charliermarsh.ruff"
@@ -59,5 +61,5 @@
5961
"initializeCommand": "sh .devcontainer/initialize-command.sh",
6062
"onCreateCommand": "sh .devcontainer/on-create-command.sh",
6163
"postStartCommand": "sh .devcontainer/post-start-command.sh"
62-
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): a16b1f65 # spellchecker:disable-line
64+
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): dfcd01a5 # spellchecker:disable-line
6365
}

.devcontainer/envs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"description": "main",
4+
"relative_directory": ".",
5+
"package_manager": "uv"
6+
}
7+
]
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import argparse
2+
import os
3+
import platform
4+
import shutil
5+
import subprocess
6+
import sys
7+
8+
UV_VERSION = "0.7.8"
9+
PNPM_VERSION = "10.11.0"
10+
COPIER_VERSION = "9.7.1"
11+
COPIER_TEMPLATES_EXTENSION_VERSION = "0.3.1"
12+
PRE_COMMIT_VERSION = "4.2.0"
13+
GITHUB_WINDOWS_RUNNER_BIN_PATH = r"C:\Users\runneradmin\.local\bin"
14+
parser = argparse.ArgumentParser(description="Install CI tooling for the repo")
15+
_ = parser.add_argument(
16+
"--no-python",
17+
default=False,
18+
action="store_true",
19+
help="Do not process any environments using python package managers",
20+
)
21+
_ = parser.add_argument(
22+
"--python-version",
23+
default=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
24+
type=str,
25+
help="What version to install.",
26+
)
27+
_ = parser.add_argument(
28+
"--no-node", action="store_true", default=False, help="Do not process any environments using node package managers"
29+
)
30+
31+
32+
def main():
33+
args = parser.parse_args(sys.argv[1:])
34+
is_windows = platform.system() == "Windows"
35+
uv_env = dict(os.environ)
36+
uv_env.update({"UV_PYTHON_PREFERENCE": "only-system", "UV_PYTHON": args.python_version})
37+
uv_path = ((GITHUB_WINDOWS_RUNNER_BIN_PATH + "\\") if is_windows else "") + "uv"
38+
if is_windows:
39+
pwsh = shutil.which("pwsh") or shutil.which("powershell")
40+
if not pwsh:
41+
raise FileNotFoundError("Neither 'pwsh' nor 'powershell' found on PATH")
42+
if not args.no_python:
43+
if is_windows:
44+
uv_env.update({"PATH": rf"{GITHUB_WINDOWS_RUNNER_BIN_PATH};{uv_env['PATH']}"})
45+
# invoke installer in a pwsh process
46+
_ = subprocess.run(
47+
[
48+
pwsh, # type: ignore[reportPossiblyUnboundVariable] # this matches the conditional above that defines pwsh
49+
"-NoProfile",
50+
"-NonInteractive",
51+
"-Command",
52+
f"irm https://astral.sh/uv/{UV_VERSION}/install.ps1 | iex",
53+
],
54+
check=True,
55+
env=uv_env,
56+
)
57+
else:
58+
_ = subprocess.run(
59+
f"curl -fsSL https://astral.sh/uv/{UV_VERSION}/install.sh | sh",
60+
check=True,
61+
shell=True,
62+
env=uv_env,
63+
)
64+
# TODO: add uv autocompletion to the shell https://docs.astral.sh/uv/getting-started/installation/#shell-autocompletion
65+
_ = subprocess.run(
66+
[
67+
uv_path,
68+
"tool",
69+
"install",
70+
f"copier=={COPIER_VERSION}",
71+
"--with",
72+
f"copier-templates-extensions=={COPIER_TEMPLATES_EXTENSION_VERSION}",
73+
],
74+
check=True,
75+
env=uv_env,
76+
)
77+
_ = subprocess.run(
78+
[
79+
uv_path,
80+
"tool",
81+
"install",
82+
f"pre-commit=={PRE_COMMIT_VERSION}",
83+
],
84+
check=True,
85+
env=uv_env,
86+
)
87+
_ = subprocess.run(
88+
[
89+
uv_path,
90+
"tool",
91+
"list",
92+
],
93+
check=True,
94+
env=uv_env,
95+
)
96+
if not args.no_node:
97+
pnpm_install_sequence = ["npm -v", f"npm install -g pnpm@{PNPM_VERSION}", "pnpm -v"]
98+
for cmd in pnpm_install_sequence:
99+
cmd = (
100+
[
101+
pwsh, # type: ignore[reportPossiblyUnboundVariable] # this matches the conditional above that defines pwsh
102+
"-NoProfile",
103+
"-NonInteractive",
104+
"-Command",
105+
cmd,
106+
]
107+
if is_windows
108+
else [cmd]
109+
)
110+
_ = subprocess.run(cmd, shell=True, check=True)
111+
112+
113+
if __name__ == "__main__":
114+
main()

.devcontainer/install-ci-tooling.sh

Lines changed: 0 additions & 26 deletions
This file was deleted.

.devcontainer/manual-setup-deps.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import argparse
2+
import enum
3+
import json
4+
import os
5+
import platform
6+
import shutil
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
from typing import Any
11+
12+
REPO_ROOT_DIR = Path(__file__).parent.parent.resolve()
13+
ENVS_CONFIG = REPO_ROOT_DIR / ".devcontainer" / "envs.json"
14+
parser = argparse.ArgumentParser(description="Manual setup for dependencies in the repo")
15+
_ = parser.add_argument(
16+
"--python-version",
17+
type=str,
18+
default=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
19+
help="What version to install.",
20+
)
21+
_ = parser.add_argument("--skip-check-lock", action="store_true", default=False, help="Skip the lock file check step")
22+
_ = parser.add_argument(
23+
"--optionally-check-lock", action="store_true", default=False, help="Check the lock file IFF it exists"
24+
)
25+
_ = parser.add_argument(
26+
"--no-python",
27+
action="store_true",
28+
default=False,
29+
help="Do not process any environments using python package managers",
30+
)
31+
_ = parser.add_argument(
32+
"--no-node", action="store_true", default=False, help="Do not process any environments using node package managers"
33+
)
34+
35+
36+
class PackageManager(str, enum.Enum):
37+
UV = "uv"
38+
PNPM = "pnpm"
39+
40+
41+
class EnvConfig:
42+
def __init__(self, json_dict: dict[str, Any]):
43+
super().__init__()
44+
self.package_manager = PackageManager(json_dict["package_manager"])
45+
self.path = REPO_ROOT_DIR
46+
if "relative_directory" in json_dict:
47+
self.path = REPO_ROOT_DIR / json_dict["relative_directory"]
48+
if self.package_manager == PackageManager.UV:
49+
self.lock_file = self.path / "uv.lock"
50+
elif self.package_manager == PackageManager.PNPM:
51+
self.lock_file = self.path / "pnpm-lock.yaml"
52+
else:
53+
raise NotImplementedError(f"Package manager {self.package_manager} is not supported")
54+
55+
56+
def main():
57+
args = parser.parse_args(sys.argv[1:])
58+
is_windows = platform.system() == "Windows"
59+
uv_env = dict(os.environ)
60+
uv_env.update({"UV_PYTHON_PREFERENCE": "only-system", "UV_PYTHON": args.python_version})
61+
skip_check_lock = args.skip_check_lock
62+
if skip_check_lock and args.optionally_check_lock:
63+
print("Cannot skip and optionally check the lock file at the same time.")
64+
sys.exit(1)
65+
66+
with ENVS_CONFIG.open("r") as f:
67+
envs = json.load(f)
68+
69+
for env_dict in envs:
70+
env = EnvConfig(env_dict)
71+
if args.no_python and env.package_manager == PackageManager.UV:
72+
print(f"Skipping environment {env.path} as it uses a Python package manager and --no-python is set")
73+
continue
74+
if args.no_node and env.package_manager == PackageManager.PNPM:
75+
print(f"Skipping environment {env.path} as it uses a Node package manager and --no-node is set")
76+
continue
77+
env_skip_check_lock = skip_check_lock
78+
if args.optionally_check_lock and env.lock_file.exists():
79+
env_skip_check_lock = False
80+
if not env_skip_check_lock:
81+
if env.package_manager == PackageManager.UV:
82+
_ = subprocess.run(["uv", "lock", "--check", "--directory", str(env.path)], check=True, env=uv_env)
83+
elif env.package_manager == PackageManager.PNPM:
84+
pass # doesn't seem to be a way to do this https://github.com/orgs/pnpm/discussions/3202
85+
else:
86+
raise NotImplementedError(f"Package manager {env.package_manager} does not support lock file checking")
87+
if env.package_manager == PackageManager.UV:
88+
sync_command = ["uv", "sync", "--directory", str(env.path)]
89+
if not env_skip_check_lock:
90+
sync_command.append("--frozen")
91+
_ = subprocess.run(
92+
sync_command,
93+
check=True,
94+
env=uv_env,
95+
)
96+
elif env.package_manager == PackageManager.PNPM:
97+
pnpm_command = ["pnpm", "install", "--dir", str(env.path)]
98+
if not env_skip_check_lock:
99+
pnpm_command.append("--frozen-lockfile")
100+
if is_windows:
101+
pwsh = shutil.which("pwsh") or shutil.which("powershell")
102+
if not pwsh:
103+
raise FileNotFoundError("Neither 'pwsh' nor 'powershell' found on PATH")
104+
pnpm_command = [
105+
pwsh,
106+
"-NoProfile",
107+
"-NonInteractive",
108+
"-Command",
109+
" ".join(pnpm_command),
110+
]
111+
_ = subprocess.run(
112+
pnpm_command,
113+
check=True,
114+
)
115+
else:
116+
raise NotImplementedError(f"Package manager {env.package_manager} is not supported for installation")
117+
118+
119+
if __name__ == "__main__":
120+
main()

.devcontainer/on-create-command-boilerplate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
set -ex
33

4-
sh .devcontainer/install-ci-tooling.sh
4+
python .devcontainer/install-ci-tooling.py
55

66
git config --global --add --bool push.autoSetupRemote true
77
git config --local core.symlinks true

.devcontainer/on-create-command.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ sh .devcontainer/on-create-command-boilerplate.sh
99

1010
pre-commit install --install-hooks
1111

12-
sh .devcontainer/manual-setup-deps.sh --optionally-lock
12+
python .devcontainer/manual-setup-deps.py --optionally-check-lock

0 commit comments

Comments
 (0)