Skip to content

Commit b3f7aa7

Browse files
authored
Add a tool bundle from PyCQA (#31)
* chore: Add PyCQA devcontainer feature and test scripts * chore: Add pycqa to the list of dependencies for devcontainer and test scripts * chore: Update devcontainer and test workflows * Update README.md with new feature: PyCQA tools bundle * chore: Update PyCQA devcontainer feature and test scripts
1 parent 6b674d8 commit b3f7aa7

File tree

9 files changed

+340
-4
lines changed

9 files changed

+340
-4
lines changed

.github/workflows/test.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
- pytest
2121
- pymarkdownlnt
2222
- pyupgrade
23+
- pycqa
2324
- rstcheck
2425
- sshpass
2526
baseImage:
@@ -28,7 +29,8 @@ jobs:
2829
- mcr.microsoft.com/devcontainers/base:debian
2930
- mcr.microsoft.com/devcontainers/base:ubuntu
3031
steps:
31-
- uses: actions/checkout@v4
32+
- name: Checkout Code
33+
uses: actions/checkout@v4
3234

3335
- name: "Install latest devcontainer CLI"
3436
run: npm install -g @devcontainers/cli
@@ -48,10 +50,12 @@ jobs:
4850
- pytest
4951
- pymarkdownlnt
5052
- pyupgrade
53+
- pycqa
5154
- rstcheck
5255
- sshpass
5356
steps:
54-
- uses: actions/checkout@v4
57+
- name: Checkout Code
58+
uses: actions/checkout@v4
5559

5660
- name: "Install latest devcontainer CLI"
5761
run: npm install -g @devcontainers/cli
@@ -63,7 +67,8 @@ jobs:
6367
runs-on: ubuntu-latest
6468
continue-on-error: true
6569
steps:
66-
- uses: actions/checkout@v4
70+
- name: Checkout Code
71+
uses: actions/checkout@v4
6772

6873
- name: "Install latest devcontainer CLI"
6974
run: npm install -g @devcontainers/cli

.github/workflows/validate.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ jobs:
88
validate:
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v4
11+
- name: Checkout Code
12+
uses: actions/checkout@v4
1213

1314
- name: "Validate devcontainer-feature.json files"
1415
uses: devcontainers/action@v1

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This repository contains following features:
66
- [ansible-lint](./src/ansible-lint/README.md): Ansible Lint
77
- [django-upgrade](./src/django-upgrade/README.md): Django-upgrade
88
- [pyadr](./src/pyadr/README.md): Python ADR
9+
- [pycqa](./src/pycqa/README.md): PyCQA tools bundle
910
- [pytest](./src/pytest/README.md): Pytest
1011
- [pymarkdownlnt](./src/pymarkdownlnt/README.md): PyMarkdownLinter
1112
- [pyupgrade](./src/pyupgrade/README.md): Pyupgrade

src/pycqa/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
# Pyupgrade (via pipx) (pyupgrade)
3+
4+
With pyupgrade you can automatically upgrade your Python code to a more modern version of Python.
5+
6+
## Example Usage
7+
8+
```json
9+
"features": {
10+
"ghcr.io/hspaans/devcontainer-features/pyupgrade:1": {}
11+
}
12+
```
13+
14+
## Options
15+
16+
| Options Id | Description | Type | Default Value |
17+
|-----|-----|-----|-----|
18+
| version | Select the version to install. | string | latest |
19+
20+
21+
22+
---
23+
24+
_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/hspaans/devcontainer-features/blob/main/src/pyupgrade/devcontainer-feature.json). Add additional notes to a `NOTES.md`._

src/pycqa/devcontainer-feature.json

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"id": "pycqa",
3+
"version": "1.0.0",
4+
"name": "PyCQA tools bundle (via pipx)",
5+
"documentationURL": "http://github.com/hspaans/devcontainer-features/tree/master/src/pycqa",
6+
"licenseURL": "http://github.com/hspaans/devcontainer-features/tree/master/LICENSE",
7+
"description": "Python Code Quality Authority tools.",
8+
"options": {
9+
"doc8_version": {
10+
"default": "latest",
11+
"description": "Select the version of doc8 to install.",
12+
"proposals": [
13+
"latest"
14+
],
15+
"type": "string"
16+
},
17+
"docformatter_version": {
18+
"default": "latest",
19+
"description": "Select the version of docformatter to install.",
20+
"proposals": [
21+
"latest"
22+
],
23+
"type": "string"
24+
},
25+
"eradicate_version": {
26+
"default": "latest",
27+
"description": "Select the version of eradicate to install.",
28+
"proposals": [
29+
"latest"
30+
],
31+
"type": "string"
32+
},
33+
"isort_version": {
34+
"default": "latest",
35+
"description": "Select the version of isort to install.",
36+
"proposals": [
37+
"latest"
38+
],
39+
"type": "string"
40+
},
41+
"pydocstyle_version": {
42+
"default": "latest",
43+
"description": "Select the version of pydocstyle to install.",
44+
"proposals": [
45+
"latest"
46+
],
47+
"type": "string"
48+
},
49+
"pyflakes_version": {
50+
"default": "latest",
51+
"description": "Select the version of pyflakes to install.",
52+
"proposals": [
53+
"latest"
54+
],
55+
"type": "string"
56+
}
57+
},
58+
"installsAfter": [
59+
"ghcr.io/devcontainers-contrib/features/pipx-package",
60+
"ghcr.io/devcontainers/features/python"
61+
]
62+
}

src/pycqa/install.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
set -e
3+
4+
. ./library_scripts.sh
5+
6+
# nanolayer is a cli utility which keeps container layers as small as possible
7+
# source code: https://github.com/devcontainers-contrib/nanolayer
8+
# `ensure_nanolayer` is a bash function that will find any existing nanolayer installations,
9+
# and if missing - will download a temporary copy that automatically get deleted at the end
10+
# of the script
11+
ensure_nanolayer nanolayer_location "v0.5.0"
12+
13+
14+
$nanolayer_location \
15+
install \
16+
devcontainer-feature \
17+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
18+
--option package='doc8' --option version="$DOC8_VERSION"
19+
20+
$nanolayer_location \
21+
install \
22+
devcontainer-feature \
23+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
24+
--option package='docformatter' --option version="$DOCFORMATTER_VERSION"
25+
26+
$nanolayer_location \
27+
install \
28+
devcontainer-feature \
29+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
30+
--option package='eradicate' --option version="$ERADICATE_VERSION"
31+
32+
$nanolayer_location \
33+
install \
34+
devcontainer-feature \
35+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
36+
--option package='isort' --option version="$ISORT_VERSION"
37+
38+
$nanolayer_location \
39+
install \
40+
devcontainer-feature \
41+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
42+
--option package='pydocstyle' --option version="$PYDOCSTYLE_VERSION"
43+
44+
$nanolayer_location \
45+
install \
46+
devcontainer-feature \
47+
"ghcr.io/devcontainers-contrib/features/pipx-package:1.1.7" \
48+
--option package='pyflakes' --option version="$PYFLAKES_VERSION"
49+
50+
echo 'Done!'

src/pycqa/library_scripts.sh

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
2+
3+
clean_download() {
4+
# The purpose of this function is to download a file with minimal impact on container layer size
5+
# this means if no valid downloader is found (curl or wget) then we install a downloader (currently wget) in a
6+
# temporary manner, and making sure to
7+
# 1. uninstall the downloader at the return of the function
8+
# 2. revert back any changes to the package installer database/cache (for example apt-get lists)
9+
# The above steps will minimize the leftovers being created while installing the downloader
10+
# Supported distros:
11+
# debian/ubuntu/alpine
12+
13+
url=$1
14+
output_location=$2
15+
tempdir=$(mktemp -d)
16+
downloader_installed=""
17+
18+
_apt_get_install() {
19+
tempdir=$1
20+
21+
# copy current state of apt list - in order to revert back later (minimize contianer layer size)
22+
cp -p -R /var/lib/apt/lists $tempdir
23+
apt-get update -y
24+
apt-get -y install --no-install-recommends wget ca-certificates
25+
}
26+
27+
_apt_get_cleanup() {
28+
tempdir=$1
29+
30+
echo "removing wget"
31+
apt-get -y purge wget --auto-remove
32+
33+
echo "revert back apt lists"
34+
rm -rf /var/lib/apt/lists/*
35+
rm -r /var/lib/apt/lists && mv $tempdir/lists /var/lib/apt/lists
36+
}
37+
38+
_apk_install() {
39+
tempdir=$1
40+
# copy current state of apk cache - in order to revert back later (minimize contianer layer size)
41+
cp -p -R /var/cache/apk $tempdir
42+
43+
apk add --no-cache wget
44+
}
45+
46+
_apk_cleanup() {
47+
tempdir=$1
48+
49+
echo "removing wget"
50+
apk del wget
51+
}
52+
# try to use either wget or curl if one of them already installer
53+
if type curl >/dev/null 2>&1; then
54+
downloader=curl
55+
elif type wget >/dev/null 2>&1; then
56+
downloader=wget
57+
else
58+
downloader=""
59+
fi
60+
61+
# in case none of them is installed, install wget temporarly
62+
if [ -z $downloader ] ; then
63+
if [ -x "/usr/bin/apt-get" ] ; then
64+
_apt_get_install $tempdir
65+
elif [ -x "/sbin/apk" ] ; then
66+
_apk_install $tempdir
67+
else
68+
echo "distro not supported"
69+
exit 1
70+
fi
71+
downloader="wget"
72+
downloader_installed="true"
73+
fi
74+
75+
if [ $downloader = "wget" ] ; then
76+
wget -q $url -O $output_location
77+
else
78+
curl -sfL $url -o $output_location
79+
fi
80+
81+
# NOTE: the cleanup procedure was not implemented using `trap X RETURN` only because
82+
# alpine lack bash, and RETURN is not a valid signal under sh shell
83+
if ! [ -z $downloader_installed ] ; then
84+
if [ -x "/usr/bin/apt-get" ] ; then
85+
_apt_get_cleanup $tempdir
86+
elif [ -x "/sbin/apk" ] ; then
87+
_apk_cleanup $tempdir
88+
else
89+
echo "distro not supported"
90+
exit 1
91+
fi
92+
fi
93+
94+
}
95+
96+
97+
ensure_nanolayer() {
98+
# Ensure existance of the nanolayer cli program
99+
local variable_name=$1
100+
101+
local required_version=$2
102+
103+
local __nanolayer_location=""
104+
105+
# If possible - try to use an already installed nanolayer
106+
if [ -z "${NANOLAYER_FORCE_CLI_INSTALLATION}" ]; then
107+
if [ -z "${NANOLAYER_CLI_LOCATION}" ]; then
108+
if type nanolayer >/dev/null 2>&1; then
109+
echo "Found a pre-existing nanolayer in PATH"
110+
__nanolayer_location=nanolayer
111+
fi
112+
elif [ -f "${NANOLAYER_CLI_LOCATION}" ] && [ -x "${NANOLAYER_CLI_LOCATION}" ] ; then
113+
__nanolayer_location=${NANOLAYER_CLI_LOCATION}
114+
echo "Found a pre-existing nanolayer which were given in env variable: $__nanolayer_location"
115+
fi
116+
117+
# make sure its of the required version
118+
if ! [ -z "${__nanolayer_location}" ]; then
119+
local current_version
120+
current_version=$($__nanolayer_location --version)
121+
122+
123+
if ! [ $current_version == $required_version ]; then
124+
echo "skipping usage of pre-existing nanolayer. (required version $required_version does not match existing version $current_version)"
125+
__nanolayer_location=""
126+
fi
127+
fi
128+
129+
fi
130+
131+
# If not previuse installation found, download it temporarly and delete at the end of the script
132+
if [ -z "${__nanolayer_location}" ]; then
133+
134+
if [ "$(uname -sm)" = 'Linux x86_64' ] || [ "$(uname -sm)" = "Linux aarch64" ]; then
135+
tmp_dir=$(mktemp -d -t nanolayer-XXXXXXXXXX)
136+
137+
clean_up () {
138+
ARG=$?
139+
rm -rf $tmp_dir
140+
exit $ARG
141+
}
142+
trap clean_up EXIT
143+
144+
145+
if [ -x "/sbin/apk" ] ; then
146+
clib_type=musl
147+
else
148+
clib_type=gnu
149+
fi
150+
151+
tar_filename=nanolayer-"$(uname -m)"-unknown-linux-$clib_type.tgz
152+
153+
# clean download will minimize leftover in case a downloaderlike wget or curl need to be installed
154+
clean_download https://github.com/devcontainers-contrib/cli/releases/download/$required_version/$tar_filename $tmp_dir/$tar_filename
155+
156+
tar xfzv $tmp_dir/$tar_filename -C "$tmp_dir"
157+
chmod a+x $tmp_dir/nanolayer
158+
__nanolayer_location=$tmp_dir/nanolayer
159+
160+
161+
else
162+
echo "No binaries compiled for non-x86-linux architectures yet: $(uname -m)"
163+
exit 1
164+
fi
165+
fi
166+
167+
# Expose outside the resolved location
168+
export ${variable_name}=$__nanolayer_location
169+
170+
}

test/pycqa/scenarios.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"test": {
3+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
4+
"features": {
5+
"pycqa": {}
6+
}
7+
}
8+
}

test/pycqa/test.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash -i
2+
3+
set -e
4+
5+
source dev-container-features-test-lib
6+
7+
check "doc8 --version" doc8 --version
8+
check "docformatter --version" docformatter --version
9+
check "eradicate --version" eradicate --version
10+
check "isort --version" isort --version
11+
check "pydocstyle --version" pydocstyle --version
12+
check "pyflakes --version" pyflakes --version
13+
14+
15+
reportResults

0 commit comments

Comments
 (0)