$npx -y skills add opendatahub-io/ai-helpers --skill python-packaging-license-checkerUse this skill to check whether a Python package license is compatible with redistribution
| 1 | # Python Package License Compatibility Checker |
| 2 | |
| 3 | Evaluate whether a Python package license permits redistribution in Red Hat products. |
| 4 | |
| 5 | **Policy basis**: Red Hat redistribution policy follows the Fedora Linux license |
| 6 | policy. Use the Fedora License Data as the authoritative source -- NOT OSI-approval |
| 7 | status. |
| 8 | |
| 9 | ## Inputs |
| 10 | |
| 11 | - **package_name**: Python package name |
| 12 | - **repo_url**: upstream repository URL (if already known — skip the source-finder step and clone this directly) |
| 13 | - **source_available**: whether buildable source exists (if already known) |
| 14 | - **upstream_org**: name and primary country of the maintaining organisation (if already known) |
| 15 | |
| 16 | ## Step 1: Locate and clone the source repository |
| 17 | |
| 18 | If a `repo_url` is provided, skip the source-finder step and use the provided URL directly with the git-shallow-clone skill. |
| 19 | |
| 20 | Otherwise, use the python-packaging-source-finder skill to find the upstream repo URL: |
| 21 | |
| 22 | ```text |
| 23 | Skill: python-packaging-source-finder |
| 24 | ``` |
| 25 | |
| 26 | Then use the git-shallow-clone skill to clone the repository locally: |
| 27 | |
| 28 | ```text |
| 29 | Skill: git-shallow-clone |
| 30 | ``` |
| 31 | |
| 32 | This gives a local path to the cloned repo for deterministic license inspection. |
| 33 | |
| 34 | ## Step 2: Read the license from source |
| 35 | |
| 36 | Search the cloned repo root for license files in this order: |
| 37 | LICENSE, LICENSE.txt, LICENSE.md, COPYING, COPYING.txt, LICENCE, LICENCE.txt |
| 38 | |
| 39 | ```bash |
| 40 | ls <clone_path>/{LICENSE,COPYING,LICENCE}{,.txt,.md} 2>/dev/null | head -1 |
| 41 | ``` |
| 42 | |
| 43 | Read the license file (up to 128 KB) and map to an SPDX identifier. If the file |
| 44 | exceeds 128 KB, read only the first 128 KB and note the truncation. If the file |
| 45 | cannot be read within 30 seconds, treat it as unreadable and set |
| 46 | Verdict: NEEDS-HUMAN-REVIEW with a note explaining the timeout. |
| 47 | |
| 48 | Also check `pyproject.toml` or `setup.cfg` for a `license` field as a secondary |
| 49 | signal. If the PyPI metadata license differs from the source file, set |
| 50 | Source verified: MISMATCH (PyPI: [X], repo: [Y]) and use the source file as |
| 51 | authoritative. |
| 52 | |
| 53 | **Normalise to SPDX ID** using exact then fuzzy matching: |
| 54 | - Deterministic: "Apache 2" -> Apache-2.0, "GPL v2" -> GPL-2.0-only, |
| 55 | "MIT License" -> MIT, "LGPLv2.1" -> LGPL-2.1-only |
| 56 | - Ambiguous (do NOT auto-map): "BSD", "GPLv3" -> set NEEDS-HUMAN-REVIEW. |
| 57 | These tokens map to multiple SPDX IDs; require human confirmation. |
| 58 | |
| 59 | **Compound expressions**: If the license contains `AND` or `OR`: |
| 60 | - `AND`: split into components; every component must be individually allowed. |
| 61 | If any single component is `not-allowed`, the whole expression is BLOCKED. |
| 62 | Document the split in Notes. |
| 63 | - `OR`: evaluate each component; use the most permissive allowed one. If none are |
| 64 | allowed, the overall verdict is the best individual verdict. |
| 65 | |
| 66 | If no license file found and no SPDX mapping possible: Verdict: NEEDS-HUMAN-REVIEW. |
| 67 | |
| 68 | Clean up the clone when done: |
| 69 | |
| 70 | ```bash |
| 71 | if [ -n "${CLONE_DIR}" ] && echo "${CLONE_DIR}" | grep -q '^/tmp/'; then |
| 72 | rm -rf -- "${CLONE_DIR}" |
| 73 | else |
| 74 | echo "ERROR: refusing to delete '${CLONE_DIR}' -- not under /tmp/" >&2 |
| 75 | fi |
| 76 | ``` |
| 77 | |
| 78 | ## Step 3: Look up license in Fedora License Data |
| 79 | |
| 80 | Fetch the live data: |
| 81 | |
| 82 | ```text |
| 83 | WebFetch: https://gitlab.com/fedora/legal/fedora-license-data/-/jobs/artifacts/main/raw/fedora-licenses.json?job=json |
| 84 | ``` |
| 85 | |
| 86 | Search for an entry matching the SPDX ID (case-insensitive on `spdx_abbrev`). |
| 87 | Each entry has a `status` field which may be a string or an array. Normalise to |
| 88 | an array and apply the following deterministic mapping. |
| 89 | |
| 90 | Fedora License Data defines these status values: |
| 91 | |
| 92 | | Status | Meaning | Outcome | |
| 93 | |---|---|---| |
| 94 | | `allowed` | Approved for Fedora packages | Proceed to Step 4 | |
| 95 | | `allowed-fonts` | Approved only for font packages | Proceed to Step 4 (add note: "License approved for fonts only; verify component type") | |
| 96 | | `allowed-documentation` | Approved only for documentation | Proceed to Step 4 (add note: "License approved for documentation only; verify component type") | |
| 97 | | `allowed-content` | Approved only for content (non-code) | Proceed to Step 4 (add note: "License approved for content only; verify component type") | |
| 98 | | `not-allowed` | Explicitly prohibited | Verdict: BLOCKED | |
| 99 | |
| 100 | Apply fail-closed logic: |
| 101 | |
| 102 | - If any entry is `"not-allowed"` -> Verdict: BLOCKED |
| 103 | - If all entries are one of the `allowed*` statuses above (and none |
| 104 | `"not-allowed"`) -> proceed to Step 4 |
| 105 | - If the `status` field is missing, empty, or contains a value not listed in |
| 106 | the table above -> Verdict: NEEDS-HUMAN-REVIEW (note the unrecognised value) |
| 107 | - SPDX ID not found in data -> Verdict: NEEDS-HUMAN-REVIEW |
| 108 | |
| 109 | **Fallback table** (use only when the JSON fetch fails): |
| 110 | |
| 111 | | Status | SPDX identifiers (representative) | |
| 112 | |---|---| |
| 113 | | allowed | MIT, Apache-2.0, BSD-2-Cla |