Skip to content

Migrate licenses.py to spec + code generation #183

@rtibbles

Description

@rtibbles

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 🤖

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions