Skip to content

Commit 9f7b5df

Browse files
authored
Merge pull request #5 from iamtodor/develop
Develop
2 parents 413bb57 + 1472a77 commit 9f7b5df

13 files changed

+93
-8
lines changed

article.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
Linter helps and advises us about code quality by running sanity checks and displaying warnings and errors about code smells. Also, potentially it helps to prevent bugs in a project.
2+
3+
As we are in FreshBooks using GitHub, so we would like to use it as much as possible.
4+
5+
Recently I was involved in configuring linters as a part of CI/CD in GitHub actions.
6+
7+
## Linters configuration
8+
9+
I would like to share how to configure it for the python project. I prepared a full [
10+
github actions python configuration demo repository](https://github.com/iamtodor/github-actions-python-demo).
11+
12+
We use flakeheaven as flake8 wrapper, which is very easy to configure in one single `pyproject.toml` configuration file. The whole `pyproject.toml` configuration file could be found in a [repo](https://github.com/iamtodor/github-actions-python-configuration-demo/blob/main/pyproject.toml).
13+
14+
![pyproject.toml](https://i.imgur.com/nNDptBv.png)
15+
16+
Disclaimer: author assumes you are familiar with above-mentioned linters, tools, and checks. I would say the config file is self-explainable, so I will not stop here for a while. Just a few notes about tiny tweaks.
17+
18+
A few checks that we don't want to see complain about:
19+
20+
### Documentation
21+
22+
We are ok if not every module will be documented. We are ok if not every function or method will be documented.
23+
24+
![flakeheaven disable docs](https://i.imgur.com/pVfPuJJ.png)
25+
26+
### Import issues
27+
28+
Our linter requirements live in a separate file, and we don't aim to mix it with our main production requirements. Hence, linter could complain about import libraries as linter env does not have production libraries, quite obvious. So we need to disable this check. We assume that the developer who writes the code and imports the libs is responsible for the tests. So if the test does not pass it means that it's something with import or a code itself. Import checks it's not something we would like to put as a linter job.
29+
30+
![flakeheaven disable import checks](https://i.imgur.com/mYVC7fj.png)
31+
32+
### Airflow
33+
34+
In order to configure code for Airflow DAGs there are also a few tweaks. Here is the dummy example `dummy.py`.
35+
36+
## TODO
37+
![flakeheaven disable import checks](https://i.imgur.com/mYVC7fj.png)
38+
39+
Firstly, we need to exclude `W0104` from pylint: Statement seems to have no effect (pointless-statement). This is about how we specify task order.
40+
41+
## TODO
42+
![flakeheaven disable import checks](https://i.imgur.com/mYVC7fj.png)
43+
44+
Then we want to have each task be specified in a new line, hence we need to disable `W503` from pycodestyle: Disable line break before binary operator.
45+
46+
## TODO
47+
![flakeheaven disable import checks](https://i.imgur.com/mYVC7fj.png)
48+
49+
## GitHub actions configurations
50+
51+
Disclaimer: author assumes you are familiar with [GitHub actions](https://github.com/features/actions)
52+
53+
We configure GitHub Workflow to be triggered on every PR against main (master) branch.
54+
55+
Here are the linters and checks we are going to use:
56+
57+
- [flake8](https://flake8.pycqa.org/en/latest/)
58+
- [flakeheaven](https://flakeheaven.readthedocs.io/en/latest/)
59+
- [black](https://github.com/psf/black)
60+
- [isort](https://github.com/PyCQA/isort)
61+
62+
The whole `py_linter.yml` config could be found in a [repo](https://github.com/iamtodor/github-actions-python-demo/blob/main/.github/workflows/py_linter.yml). We will walk thru it step by step.
63+
64+
![py_linter.yml](https://i.imgur.com/UkErWeG.png)
65+
66+
We are interested in running linter only when PR has `.py` files. For instance, when we update `README.md` there is no sense to run python linter.
67+
68+
![configure run workflow on PRs and push](https://i.imgur.com/4B5JLqi.png)
69+
70+
We are interested in running a linter only against the modified files. Let's say, we take a look at the provided repo, if I update `dags/dummy.py` I don't want to waste a time and resources running linter against `main.py`. For this purpose we use [Paths Filter GitHub Action](https://github.com/dorny/paths-filter), that is very flexible.
71+
72+
If we have in one PR modified `.py` and `.toml` files, we don't want to run linter against `.toml`, so we use where we configured filtering only for `.py` files no matter its location: root, tests, src, etc.
73+
74+
![Paths Filter GitHub Action](https://i.imgur.com/B9lu9ki.png)
75+
76+
As a changed file can be `added`, `modified`, or `deleted`, there is no reason to run a linter against deleted file as your workflow would simply fail as there is no more than that particular changed file. So we need to configure what changes we consider to trigger linter.
77+
78+
![added|modified](https://i.imgur.com/YBrd8Ee.png)
79+
80+
I define the variable where I can find the output (the only `.py` files) from the previous filter. This variable would contain modified `.py` files, that I can further pass to a `flakeheaven`, `black`, and `isort`. By default, the output is disabled, and Paths Changes Filter allows to customize it: you can list the files in `.csv`, `.json` or in a `shell` mode. Linters accept files separated simply by space, so our choise here is `shell` mode.
81+
82+
![list files shell](https://i.imgur.com/0HjT6Wg.png)

artricle/img/added-modified.png

68.3 KB
Loading
70.3 KB
Loading

artricle/img/diable-line-break.png

87.6 KB
Loading
151 KB
Loading
Loading

artricle/img/list-files-shell.png

66.9 KB
Loading

artricle/img/paths-filter.png

361 KB
Loading

artricle/img/py-push-pr.png

63.3 KB
Loading

artricle/img/pyproject.toml.png

509 KB
Loading
294 KB
Loading

artricle/img/whole-config.png

381 KB
Loading

pyproject.toml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ exclude = '''
88
| \.mypy_cache
99
| \.pytest_cache
1010
| \.idea
11-
| \.?v?env
11+
| venv
12+
| venv_linter
13+
| venv_tests
1214
| \.local
1315
| makefile\.inc
14-
| venv_linter
1516
| build
1617
| dist
1718
)/
@@ -20,20 +21,22 @@ exclude = '''
2021
[tool.isort]
2122
profile = 'black'
2223
line_length = 120
23-
skip_glob = ['.local']
24+
skip_glob = [
25+
".history",
26+
"venv",
27+
"venv_linter",
28+
"venv_tests",
29+
]
2430
use_parentheses = true
25-
#force_grip_wrap = 0
2631
multi_line_output = 3
2732
include_trailing_comma = true
2833

2934
[tool.flakeheaven]
3035
exclude = [
3136
".history",
32-
".venv",
33-
".env",
3437
"venv",
3538
"venv_linter",
36-
"env"
39+
"venv_tests",
3740
]
3841
format = "grouped"
3942
max_line_length = 120
@@ -62,4 +65,4 @@ pycodestyle = [
6265
]
6366
pylint = [
6467
"-W0104" # disable statement seems to have no effect (pointless-statement)
65-
]
68+
]

0 commit comments

Comments
 (0)