learningequality / learningequality/le-utils

Migrate licenses.py to spec + code generation

Open
#183 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
7
Forks
36
PR merge metrics
No merged PRs in 30d

Description

This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

Migrate le_utils/constants/licenses.py from the legacy JSON-as-data approach to the modern spec + code generation system following the pattern established in #182.

Context

Currently, le_utils/constants/licenses.py uses the legacy approach:

  • Loads resources/licenselookup.json at runtime with pkgutil.get_data()
  • Manual Python constants (CC_BY = "CC BY", etc.) must be kept in sync
  • No JavaScript export available
  • Tests verify Python/JSON sync
Current Structure

File: le_utils/resources/licenselookup.json

{
  "1": {
    "name": "CC BY",
    "exists": true,
    "custom": false,
    "copyright_holder_required": true,
    "url": "https://creativecommons.org/licenses/by/4.0/"
  },
  "2": {
    "name": "CC BY-SA",
    ...
  },
  ...
}

Python module has:

  • Namedtuple: class License(namedtuple("License", ["id", "name", "exists", "url", "description", "custom", "copyright_holder_required"])): pass
  • Manual constants: CC_BY = "CC BY", CC_BY_SA = "CC BY-SA", etc.
  • LICENSELIST with License namedtuples
  • choices tuple
Target Spec Format

Create spec/constants-licenses.json:

{
  "namedtuple": {
    "name": "License",
    "fields": ["id", "name", "exists", "url", "description", "custom", "copyright_holder_required"]
  },
  "constants": {
    "1": {
      "name": "CC BY",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by/4.0/",
      "description": ""
    },
    "2": {
      "name": "CC BY-SA",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by-sa/4.0/",
      "description": ""
    },
    "3": {
      "name": "CC BY-ND",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by-nd/4.0/",
      "description": ""
    },
    "4": {
      "name": "CC BY-NC",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by-nc/4.0/",
      "description": ""
    },
    "5": {
      "name": "CC BY-NC-SA",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by-nc-sa/4.0/",
      "description": ""
    },
    "6": {
      "name": "CC BY-NC-ND",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "https://creativecommons.org/licenses/by-nc-nd/4.0/",
      "description": ""
    },
    "7": {
      "name": "All Rights Reserved",
      "exists": true,
      "custom": false,
      "copyright_holder_required": true,
      "url": "http://www.allrights-reserved.com/",
      "description": ""
    },
    "8": {
      "name": "Public Domain",
      "exists": true,
      "custom": false,
      "copyright_holder_required": false,
      "url": "https://creativecommons.org/publicdomain/mark/1.0/",
      "description": ""
    },
    "9": {
      "name": "Special Permissions",
      "exists": false,
      "custom": true,
      "copyright_holder_required": true,
      "url": "",
      "description": ""
    }
  }
}

Note: The description field is in the namedtuple but currently empty in the JSON. Keep it as empty string for now.

Generated Output Example

Python (le_utils/constants/licenses.py):

# Generated by scripts/generate_from_specs.py
from collections import namedtuple

class License(namedtuple("License", ["id", "name", "exists", "url", "description", "custom", "copyright_holder_required"])):
    pass

CC_BY = "CC BY"
CC_BY_SA = "CC BY-SA"
# ...

choices = (
    (CC_BY, "Cc By"),
    (CC_BY_SA, "Cc By-Sa"),
    # ...
)

LICENSELIST = [
    License(id=1, name="CC BY", exists=True, url="...", description="", custom=False, copyright_holder_required=True),
    # ...
]

JavaScript (js/Licenses.js):

// Generated by scripts/generate_from_specs.py

export default {
    CC_BY: "CC BY",
    CC_BY_SA: "CC BY-SA",
    // ...
};

export const LicensesList = [
    { id: 1, name: "CC BY", exists: true, url: "...", description: "", custom: false, copyright_holder_required: true },
    // ...
];

export const LicensesMap = new Map(
    LicensesList.map(license => [license.id, license])
);
Testing Updates

File: tests/test_licenses.py

Update to test against spec:

spec_path = os.path.join(os.path.dirname(__file__), "..", "spec", "constants-licenses.json")
with open(spec_path) as f:
    spec = json.load(f)
    licenselookup = spec["constants"]
How to Run Tests
pytest tests/test_licenses.py -v
pytest tests/ -v
Acceptance Criteria
  • spec/constants-licenses.json created with all license data
  • make build successfully generates Python and JavaScript files
  • Generated le_utils/constants/licenses.py has:
    • License namedtuple with 7 fields
    • Uppercase constants (CC_BY, CC_BY_SA, etc.)
    • choices tuple
    • LICENSELIST with License namedtuples
  • Generated js/Licenses.js has:
    • Default export with constants
    • LicensesList with full license data
    • LicensesMap for lookups
  • tests/test_licenses.py updated to test against spec
  • All tests pass
  • resources/licenselookup.json deleted

Disclosure

🤖 This issue was written by Claude Code, under supervision, review and final edits by @rtibbles 🤖

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing the migration pattern in #182 with le_utils/constants/licenses.py and resources/licenselookup.json. Create spec/constants-licenses.json, update tests/test_licenses.py, and run pytest tests/test_licenses.py -v before running make build. Done means generated Python and JavaScript outputs contain the specified constants and license data, resources/licenselookup.json is removed, and the full test suite passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, json, python
Domain
build-system, testing, tooling
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.