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

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions