Skip to content

Commit 521d6ca

Browse files
committed
ci: align linting with project guidelines
Adopt the Ruff-based linting and formatting baseline discussed in kernelci-project#566. Replace the existing pycodestyle-only CI step with pre-commit-enforced Ruff checks, keep the YAML and JWT validators in place, and pin the local developer tooling used by CI. Set a shared 80/110 line-length policy, document the project-level ignore list, allow scoped complexity exceptions for legacy entrypoints, and reformat the Python tree to establish a clean baseline. Refs: kernelci-project#566 Signed-off-by: Denys Fedoryshchenko <denys.f@collabora.com>
1 parent 5580a78 commit 521d6ca

33 files changed

Lines changed: 2146 additions & 1598 deletions

.github/workflows/main.yml

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
1-
# This is a basic workflow to help you get started with Actions
2-
31
name: CI
42

5-
# Controls when the workflow will run
63
on:
7-
# Triggers the workflow on push or pull request events but only for the main branch
84
push:
95
branches: [ main ]
106
pull_request:
117
branches: [ main ]
12-
13-
# Allows you to run this workflow manually from the Actions tab
148
workflow_dispatch:
159

16-
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
1710
jobs:
18-
check:
11+
lint:
12+
runs-on: ubuntu-22.04
13+
14+
steps:
15+
- name: Check out source code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: '3.13'
22+
cache: 'pip'
23+
24+
- name: Install lint tooling
25+
run: |
26+
python --version
27+
python -m pip install --upgrade pip
28+
pip install -r requirements-dev.txt
29+
30+
- name: Run Ruff pre-commit hooks
31+
run: |
32+
pre-commit run --all-files --show-diff-on-failure
33+
34+
validate:
1935
runs-on: ubuntu-22.04
2036
strategy:
2137
matrix:
@@ -25,7 +41,6 @@ jobs:
2541
- '3.13'
2642

2743
steps:
28-
2944
- name: Check out source code
3045
uses: actions/checkout@v4
3146

@@ -35,34 +50,16 @@ jobs:
3550
python-version: ${{ matrix.python-version }}
3651
cache: 'pip'
3752

38-
# Override version of pycodestyle for Python 3.7 to the
39-
# latest available release that can be installed on this
40-
# old Python release.
41-
- name: Override pycodestyle version
42-
if: ${{ matrix.python-version == '3.7' }}
43-
run: |
44-
echo 'pycodestyle==2.10.0' > requirements-dev.txt
45-
46-
- name: Install Python packages
53+
- name: Install validation dependencies
4754
run: |
4855
python --version
4956
python -m pip install --upgrade pip
50-
pip install -r requirements-dev.txt
51-
52-
- name: Run pycodestyle
53-
run: |
54-
pycodestyle --ignore=E501,W503,W504 src/*.py
55-
56-
- name: Install Python YAML package
57-
run: |
58-
pip install pyyaml
57+
pip install pyyaml PyJWT toml
5958
6059
- name: Run basic YAML validation
6160
run: |
6261
python tests/validate_yaml.py
6362
6463
- name: Run basic JWT test
6564
run: |
66-
pip install PyJWT
67-
pip install toml
6865
python tests/validate_jwt.py

.pre-commit-config.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: ruff-check
5+
name: ruff check
6+
entry: ruff check --fix
7+
language: system
8+
types_or: [python, pyi]
9+
- id: ruff-format
10+
name: ruff format
11+
entry: ruff format
12+
language: system
13+
types_or: [python, pyi]

pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[tool.ruff]
2+
target-version = "py37"
3+
src = ["src", "tests", "tools"]
4+
line-length = 80
5+
6+
[tool.ruff.lint]
7+
select = ["C901", "E", "F", "I", "W"]
8+
ignore = [
9+
"E203", # Formatter-compatible slicing whitespace.
10+
]
11+
12+
[tool.ruff.lint.pycodestyle]
13+
max-line-length = 110
14+
15+
[tool.ruff.lint.per-file-ignores]
16+
# Legacy or high-churn entrypoints are allowed incremental complexity cleanup.
17+
"src/job_retry.py" = ["C901"]
18+
"src/lava_callback.py" = ["C901", "E501"]
19+
"src/scheduler.py" = ["C901"]
20+
"src/send_kcidb.py" = ["C901"]
21+
"src/trigger.py" = ["C901"]
22+
"tests/hw_reg_checker.py" = ["C901"]
23+
"tests/validate_yaml.py" = ["C901"]
24+
"tools/example_pull_lab.py" = ["C901"]
25+
"tools/example_pull_lab_pytest.py" = ["C901"]

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
pycodestyle==2.14.0
1+
pre-commit==4.5.1
2+
ruff==0.15.9

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[pycodestyle]
2-
max-line-length = 100
2+
max-line-length = 110

src/base.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
import logging
99
import os
10-
import requests
1110

1211
import kernelci
12+
import requests
1313
from kernelci.api.helper import APIHelper
1414

1515
from logger import Logger
1616

17-
SERVICE_PIPELINE = 'service:pipeline'
17+
SERVICE_PIPELINE = "service:pipeline"
1818

1919

2020
class Service:
@@ -23,8 +23,8 @@ class Service:
2323
def __init__(self, configs, args, name):
2424
self._name = name
2525
self._logger = Logger("config/logger.conf", name)
26-
self._api_config = configs['api'][args.api_config]
27-
api_token = os.getenv('KCI_API_TOKEN')
26+
self._api_config = configs["api"][args.api_config]
27+
api_token = os.getenv("KCI_API_TOKEN")
2828
self._api = kernelci.api.get_api(self._api_config, api_token)
2929
self._api_helper = APIHelper(self._api)
3030

@@ -82,20 +82,20 @@ def run(self, args=None):
8282

8383

8484
def validate_url(url, timeout=5):
85-
'''
85+
"""
8686
Validate URL by:
8787
- checking if it's not empty
8888
- checking if it starts with HTTP* scheme
8989
- requesting HEAD to see if it can be accessed
90-
'''
90+
"""
9191
if not url:
9292
return False
93-
if not url.startswith('http'):
93+
if not url.startswith("http"):
9494
return False
9595
try:
9696
r = requests.head(url, allow_redirects=True, timeout=timeout)
9797
r.raise_for_status()
9898
except requests.exceptions.RequestException as e:
99-
logging.error(f'Error accessing URL: {e}')
99+
logging.error(f"Error accessing URL: {e}")
100100
return False
101101
return True

src/job_retry.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,39 @@
66
# Author: Jeny Sadadia <jeny.sadadia@collabora.com>
77

88
import sys
9+
910
import kernelci.config
1011
from kernelci.legacy.cli import Args, Command, parse_opts
1112

1213
from base import Service
1314

1415

1516
class JobRetry(Service):
16-
1717
def __init__(self, configs, args):
18-
super().__init__(configs, args, 'job_retry')
18+
super().__init__(configs, args, "job_retry")
1919

2020
def _setup(self, args):
21-
return self._api_helper.subscribe_filters({
22-
"state": "done",
23-
"result": ("incomplete", "fail"),
24-
"kind": ("kbuild", "job"),
25-
})
21+
return self._api_helper.subscribe_filters(
22+
{
23+
"state": "done",
24+
"result": ("incomplete", "fail"),
25+
"kind": ("kbuild", "job"),
26+
}
27+
)
2628

2729
def _stop(self, sub_id):
2830
if sub_id:
2931
self._api_helper.unsubscribe_filters(sub_id)
3032
sys.stdout.flush()
3133

3234
def _find_parent_kind(self, node, api_helper, kind):
33-
parent_id = node.get('parent')
35+
parent_id = node.get("parent")
3436
if not parent_id:
3537
return None
3638
parent_node = api_helper.api.node.get(parent_id)
3739
if not parent_node:
3840
return None
39-
if parent_node.get('kind') == kind:
41+
if parent_node.get("kind") == kind:
4042
return parent_node
4143
return self._find_parent_kind(parent_node, api_helper, kind)
4244

@@ -59,10 +61,14 @@ def _run(self, sub_id):
5961

6062
# Check retry count before submitting a retry
6163
retry_counter = node.get("retry_counter", 0)
62-
self.log.debug(f"{node['id']}: Node current retry_counter: {retry_counter}")
64+
self.log.debug(
65+
f"{node['id']}: Node current retry_counter: {retry_counter}"
66+
)
6367
if retry_counter >= 3:
64-
self.log.info(f"{node['id']} Job has already retried 3 times. \
65-
Not submitting a retry.")
68+
self.log.info(
69+
f"{node['id']} Job has already retried 3 times. \
70+
Not submitting a retry."
71+
)
6672
continue
6773

6874
parent_kind = None
@@ -71,27 +77,39 @@ def _run(self, sub_id):
7177
if node.get("kind") == "kbuild":
7278
parent_kind = "checkout"
7379
if parent_kind:
74-
event_data = self._find_parent_kind(node, self._api_helper, parent_kind)
80+
event_data = self._find_parent_kind(
81+
node, self._api_helper, parent_kind
82+
)
7583
if not event_data:
76-
self.log.error(f"Not able to find parent node for {node['id']}")
84+
self.log.error(
85+
f"Not able to find parent node for {node['id']}"
86+
)
7787
continue
7888
if node["kind"] == "kbuild":
79-
event_data["jobfilter"] = [f'{node["name"]}+']
89+
event_data["jobfilter"] = [f"{node['name']}+"]
8090
else:
8191
event_data["jobfilter"] = [node["name"]]
8292
# Change event data state to available to trigger jobs based on scheduler configs
8393
event_data["state"] = "available"
8494
if node["kind"] == "job":
85-
event_data["platform_filter"] = [node["data"].get("platform")]
95+
event_data["platform_filter"] = [
96+
node["data"].get("platform")
97+
]
8698
event_data["retry_counter"] = retry_counter + 1
8799
event_data["debug"] = {"retry_by": str(node["id"])}
88-
self.log.debug(f"{node['id']}:Event data retry_counter: {event_data['retry_counter']}")
89-
event = {'data': event_data}
90-
self._api_helper.api.send_event('retry', event)
91-
self.log.info(f"Job retry for node {node['id']} submitted. Parent node: {event_data['id']}")
100+
self.log.debug(
101+
f"{node['id']}:Event data retry_counter: {event_data['retry_counter']}"
102+
)
103+
event = {"data": event_data}
104+
self._api_helper.api.send_event("retry", event)
105+
self.log.info(
106+
f"Job retry for node {node['id']} submitted. Parent node: {event_data['id']}"
107+
)
92108
self.log.debug(f"Event:{event}")
93109
else:
94-
self.log.error(f"Not able to retry the job as parent kind is unknown: {node['id']}")
110+
self.log.error(
111+
f"Not able to retry the job as parent kind is unknown: {node['id']}"
112+
)
95113
return True
96114

97115

@@ -103,9 +121,9 @@ def __call__(self, configs, args):
103121
return JobRetry(configs, args).run(args)
104122

105123

106-
if __name__ == '__main__':
107-
opts = parse_opts('job_retry', globals())
108-
yaml_configs = opts.get_yaml_configs() or 'config'
124+
if __name__ == "__main__":
125+
opts = parse_opts("job_retry", globals())
126+
yaml_configs = opts.get_yaml_configs() or "config"
109127
configs = kernelci.config.load(yaml_configs)
110128
status = opts.command(configs, opts)
111129
sys.exit(0 if status is True else 1)

src/kernelci_pipeline/email_sender.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@
77

88
"""SMTP Email Sender module"""
99

10-
from email.mime.multipart import MIMEMultipart
1110
import email
1211
import email.mime.text
1312
import os
1413
import smtplib
14+
from email.mime.multipart import MIMEMultipart
1515

1616

1717
class EmailSender:
1818
"""Class to send email report using SMTP"""
19+
1920
def __init__(self, smtp_host, smtp_port, email_sender, email_recipient):
2021
self._smtp_host = smtp_host
2122
self._smtp_port = smtp_port
2223
self._email_sender = email_sender
2324
self._email_recipient = email_recipient
24-
self._email_user = os.getenv('EMAIL_USER')
25-
self._email_pass = os.getenv('EMAIL_PASSWORD')
25+
self._email_user = os.getenv("EMAIL_USER")
26+
self._email_pass = os.getenv("EMAIL_PASSWORD")
2627

2728
def _smtp_connect(self):
2829
"""Method to create a connection with SMTP server"""
@@ -39,15 +40,15 @@ def _create_email(self, email_subject, email_content):
3940
sender, and receiver"""
4041
email_msg = MIMEMultipart()
4142
email_text = email.mime.text.MIMEText(email_content, "plain", "utf-8")
42-
email_text.replace_header('Content-Transfer-Encoding', 'quopri')
43-
email_text.set_payload(email_content, 'utf-8')
43+
email_text.replace_header("Content-Transfer-Encoding", "quopri")
44+
email_text.set_payload(email_content, "utf-8")
4445
email_msg.attach(email_text)
4546
if isinstance(self._email_recipient, list):
46-
email_msg['To'] = ','.join(self._email_recipient)
47+
email_msg["To"] = ",".join(self._email_recipient)
4748
else:
48-
email_msg['To'] = self._email_recipient
49-
email_msg['From'] = self._email_sender
50-
email_msg['Subject'] = email_subject
49+
email_msg["To"] = self._email_recipient
50+
email_msg["From"] = self._email_sender
51+
email_msg["Subject"] = email_subject
5152
return email_msg
5253

5354
def _send_email(self, email_msg):
@@ -59,7 +60,5 @@ def _send_email(self, email_msg):
5960

6061
def create_and_send_email(self, email_subject, email_content):
6162
"""Method to create and send email"""
62-
email_msg = self._create_email(
63-
email_subject, email_content
64-
)
63+
email_msg = self._create_email(email_subject, email_content)
6564
self._send_email(email_msg)

0 commit comments

Comments
 (0)