Skip to content

Commit 83f54bc

Browse files
authored
feat: Adds possibility to have a custom license notice (#15)
* ci: Updates precommit hooks * feat: Adds option to have a custom license notice * ci: Updates CI * docs: Updates README & CONTRIBUTING * docs: Updated docs * style: Fixed typing * docs: Updates copyright notice
1 parent d80cf77 commit 83f54bc

8 files changed

Lines changed: 92 additions & 68 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ jobs:
2222
- name: Build docker
2323
run: docker build . -t header-validator:py3.8.13-alpine
2424
- name: Run action
25-
run: docker run --workdir /github/workspace -v "/home/runner/work/validate-python-headers/validate-python-headers":"/github/workspace" header-validator:py3.8.13-alpine Apache-2.0 'François-Guillaume Fernandez' 2022 src/ __init__.py .github/
25+
run: docker run --workdir /github/workspace -v "/home/runner/work/validate-python-headers/validate-python-headers":"/github/workspace" header-validator:py3.8.13-alpine 'François-Guillaume Fernandez' 2022 Apache-2.0 src/ __init__.py .github/ ''

.pre-commit-config.yaml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,31 @@ repos:
33
rev: v4.3.0
44
hooks:
55
- id: check-yaml
6+
exclude: .conda
67
- id: check-toml
8+
- id: check-added-large-files
79
- id: end-of-file-fixer
810
- id: trailing-whitespace
11+
- id: check-ast
12+
- id: debug-statements
13+
- id: check-json
14+
- id: check-merge-conflict
15+
- id: no-commit-to-branch
16+
- id: debug-statements
17+
language_version: python3
918
- repo: https://github.com/psf/black
1019
rev: 22.3.0
1120
hooks:
12-
- id: black
21+
- id: black
1322
- repo: https://github.com/pycqa/isort
1423
rev: 5.10.1
1524
hooks:
16-
- id: isort
25+
- id: isort
26+
- repo: https://github.com/PyCQA/flake8
27+
rev: 4.0.1
28+
hooks:
29+
- id: flake8
1730
- repo: https://github.com/PyCQA/autoflake
1831
rev: v1.7.7
1932
hooks:
20-
- id: autoflake
33+
- id: autoflake

CONTRIBUTING.md

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,39 +85,10 @@ To run all quality checks together
8585
make quality
8686
```
8787

88-
##### Lint verification
89-
90-
To ensure that your incoming PR complies with the lint settings, you need to install [flake8](https://flake8.pycqa.org/en/latest/) and run the following command from the repository's root folder:
91-
92-
```shell
93-
flake8 ./
94-
```
95-
This will read the `.flake8` setting file and let you know whether your commits need some adjustments.
96-
97-
##### Import order
98-
99-
In order to ensure there is a common import order convention, run [isort](https://github.com/PyCQA/isort) as follows:
100-
101-
```shell
102-
isort .
103-
```
104-
This will reorder the imports of your local files.
105-
106-
##### Annotation typing
107-
108-
Additionally, to catch type-related issues and have a cleaner codebase, annotation typing are expected. After installing [mypy](https://github.com/python/mypy), you can run the verifications as follows:
109-
110-
```shell
111-
mypy
112-
```
113-
The `pyproject.toml` file will be read to check your typing.
114-
115-
##### Code formatting
116-
117-
Finally, code formatting is a good practice for shareable projects. After installing [black](https://github.com/psf/black), you can run the verifications as follows:
88+
The previous command won't modify anything in your codebase. Some fixes (import ordering and code formatting) can be done automatically using the following command:
11889

11990
```shell
120-
black .
91+
make style
12192
```
12293

12394
### Submit your modifications

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ build:
2020
# Run tests for the library
2121
test:
2222
docker build . -t header-validator:py3.8.13-alpine
23-
docker run --workdir /github/workspace -v src:/github/workspace/src header-validator:py3.8.13-alpine Apache-2.0 'François-Guillaume Fernandez' 2022 src/ __init__.py .github/
23+
docker run --workdir /github/workspace -v src:/github/workspace/src header-validator:py3.8.13-alpine 'François-Guillaume Fernandez' 2022 Apache-2.0 src/ __init__.py .github/ ''

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ This action checks the copyright and license notices in the headers of your Pyth
2121

2222
## Inputs
2323

24-
### `license`
25-
26-
**Required** Identifier of the license for your project (cf. [SPDX identifiers](https://spdx.org/licenses/)).
27-
2824
### `owner`
2925

3026
**Required** The copyright owner.
@@ -33,6 +29,10 @@ This action checks the copyright and license notices in the headers of your Pyth
3329

3430
**Required** The starting year of your project.
3531

32+
### `license`
33+
34+
Identifier of the license for your project (cf. [SPDX identifiers](https://spdx.org/licenses/)). Default `null`.
35+
3636
### `folders`
3737

3838
The folders to inspect, separated by a comma. Default `"."`.
@@ -45,11 +45,17 @@ The files to ignore, separated by a comma. Default `"__init__.py"`.
4545

4646
The folders to ignore, separated by a comma. Default `".github/"`.
4747

48+
### `license-notice`
49+
50+
The path to a license notice text. If license is `null`, the header will be expected to have this text as a license notice. Default `null`.
51+
4852
## Outputs
4953

5054
The list of files with header issues.
5155

52-
## Example usage
56+
## Example usages
57+
58+
Using an Open-source license:
5359

5460
```
5561
uses: frgfm/validate-python-headers@main
@@ -61,6 +67,18 @@ with:
6167
ignore-folders: '.github/'
6268
```
6369

70+
On closed source code:
71+
72+
```
73+
uses: frgfm/validate-python-headers@main
74+
with:
75+
license-notice: '.github/license-notice.txt'
76+
owner: 'François-Guillaume Fernandez'
77+
starting-year: 2022
78+
ignore-files: 'version.py,__init__.py'
79+
ignore-folders: '.github/'
80+
```
81+
6482

6583
## Contributing
6684

action.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ branding:
66
color: blue
77

88
inputs:
9-
license:
10-
# https://spdx.org/licenses/
11-
description: 'License of your project'
12-
required: true
139
owner:
1410
description: 'Name of the copyright owner'
1511
required: true
1612
starting-year:
1713
description: 'Starting year of your project'
1814
required: true
15+
license:
16+
# https://spdx.org/licenses/
17+
description: 'License of your project'
18+
required: false
19+
default: null
1920
folders:
2021
description: 'Folders to inspect'
2122
required: false
@@ -28,6 +29,10 @@ inputs:
2829
description: 'Folders to ignore'
2930
required: false
3031
default: '.github/'
32+
license-notice:
33+
description: 'Custom license notice to be used if license is null'
34+
required: false
35+
default: null
3136

3237
outputs:
3338
issues: # id of output
@@ -37,9 +42,10 @@ runs:
3742
using: 'docker'
3843
image: 'Dockerfile'
3944
args:
40-
- ${{ inputs.license }}
4145
- ${{ inputs.owner }}
4246
- ${{ inputs.starting-year }}
47+
- ${{ inputs.license }}
4348
- ${{ inputs.folders }}
4449
- ${{ inputs.ignore-files }}
4550
- ${{ inputs.ignore-folders }}
51+
- ${{ inputs.license-notice }}

entrypoint.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/sh -l
22
set -eax
33

4-
python /validate_headers.py "${1}" "${2}" "${3}" --folders "${4}" --ignore-files "${5}" --ignore-folders "${6}"
4+
python /validate_headers.py "${1}" "${2}" --license "${3}" --folders "${4}" --ignore-files "${5}" --ignore-folders "${6}" --license-notice "${7}"

src/validate_headers.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2022, François-Guillaume Fernandez.
1+
# Copyright (C) 2022-2023, François-Guillaume Fernandez.
22

33
# This program is licensed under the Apache License 2.0.
44
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0> for full license details.
@@ -7,7 +7,7 @@
77
import json
88
from datetime import datetime
99
from pathlib import Path
10-
from typing import Dict, List
10+
from typing import Dict, List, Union
1111

1212
SHEBANG = ["#!usr/bin/python\n"]
1313
BLANK_LINE = "\n"
@@ -19,32 +19,46 @@
1919
}
2020

2121

22-
def get_header_options(license_id: str, owner: str, starting_year: int) -> List[List[str]]:
22+
def get_header_options(
23+
owner: str, starting_year: int, license_id: Union[str, None], license_notice: Union[str, None]
24+
) -> List[List[str]]:
2325

2426
# Year check
2527
current_year = datetime.now().year
26-
assert starting_year <= current_year, f"Invalid first copyright year: {starting_year}"
27-
28-
# License check
29-
license_info = LICENSES.get(license_id)
30-
assert isinstance(license_info, dict), f"Invalid license identifier: {license_id}"
28+
if starting_year > current_year:
29+
raise ValueError(f"Invalid first copyright year: {starting_year}")
3130

3231
# Owner check
33-
assert len(owner) > 0, "Please specify the copyright owner"
32+
if len(owner) == 0:
33+
raise ValueError("Please specify the copyright owner")
3434

35-
# License file check
36-
assert Path("LICENSE").is_file(), "Unable to locate local copy of license text."
35+
# License check
36+
license_notices = []
37+
if isinstance(license_id, str):
38+
license_info = LICENSES.get(license_id)
39+
if not isinstance(license_info, dict):
40+
raise KeyError(f"Invalid license identifier: {license_id}")
41+
# License file check
42+
if not Path("LICENSE").is_file():
43+
raise FileNotFoundError("Unable to locate local copy of license text.")
44+
license_notices = [
45+
[
46+
f"# This program is licensed under the {license_info['name']}.\n",
47+
f"# See LICENSE or go to <{url}> for full license details.\n",
48+
]
49+
for url in license_info["urls"]
50+
]
51+
elif isinstance(license_notice, str):
52+
if not Path(license_notice).is_file():
53+
raise FileNotFoundError("Unable to locate the text of the license notice.")
54+
with open(license_notice, "r") as f:
55+
license_notices = [f.readlines()]
56+
else:
57+
raise ValueError("One of the following args needs to be specified: 'license_id', 'license_notice'")
3758

3859
# Header build
3960
year_options = [f"{current_year}"] + [f"{year}-{current_year}" for year in range(starting_year, current_year)]
4061
copyright_notices = [[f"# Copyright (C) {year_str}, {owner}.\n"] for year_str in year_options]
41-
license_notices = [
42-
[
43-
f"# This program is licensed under the {license_info['name']}.\n",
44-
f"# See LICENSE or go to <{url}> for full license details.\n",
45-
]
46-
for url in license_info["urls"]
47-
]
4862

4963
return [
5064
SHEBANG + [BLANK_LINE] + copyright_notice + [BLANK_LINE] + license_notice
@@ -60,7 +74,7 @@ def get_header_options(license_id: str, owner: str, starting_year: int) -> List[
6074
def main(args):
6175

6276
# Check args & define all header options
63-
header_options = get_header_options(args.license, args.owner, args.year)
77+
header_options = get_header_options(args.owner, args.year, args.license, args.license_notice)
6478

6579
ignored_files = args.ignore_files.split(",")
6680
ignored_folders = [Path(folder) for folder in args.ignore_folders.split(",")]
@@ -71,7 +85,8 @@ def main(args):
7185
# For every python file in the repository
7286
for folder in folders:
7387
folder_path = Path(folder)
74-
assert folder_path.is_dir(), f"Invalid folder path: {folder}"
88+
if not folder_path.is_dir():
89+
raise FileNotFoundError(f"Invalid folder path: {folder}")
7590
for source_path in folder_path.rglob("**/*.py"):
7691
if source_path.name in ignored_files or any(folder in source_path.parents for folder in ignored_folders):
7792
continue
@@ -103,12 +118,13 @@ def parse_args():
103118
description="Header validator for your Python files", formatter_class=argparse.ArgumentDefaultsHelpFormatter
104119
)
105120

106-
parser.add_argument("license", type=str, help="identifier of the license being used")
107121
parser.add_argument("owner", type=str, help="name of the copyright owner")
108122
parser.add_argument("year", type=int, help="first copyright year of the project")
123+
parser.add_argument("--license", type=str, default=None, help="identifier of the license being used")
109124
parser.add_argument("--folders", type=str, default=".", help="folders to inspect")
110125
parser.add_argument("--ignore-files", type=str, default="", help="files to ignore")
111126
parser.add_argument("--ignore-folders", type=str, default="", help="folders to ignore")
127+
parser.add_argument("--license-notice", type=str, default=None, help="path to custom license notice")
112128
args = parser.parse_args()
113129

114130
return args

0 commit comments

Comments
 (0)