learningequality / learningequality/le-utils

Migrate content_kinds.py to spec + code generation with metadata support

Open
#184 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/content_kinds.py from the legacy JSON-as-data approach to the modern spec + code generation system. This issue also enhances the generation script to support metadata-driven code generation for the MAPPING dict.

Context

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

  • Loads resources/kindlookup.json at runtime
  • Manual Python constants (TOPIC = "topic", VIDEO = "video", etc.)
  • Manual MAPPING dict mapping file formats to content kinds (not from JSON!)
  • No JavaScript export available
Current Structure

File: le_utils/resources/kindlookup.json
(Only has kind names, no mapping data)

{
  "topic": {"name": "topic"},
  "video": {"name": "video"},
  ...
}

Python module has manual MAPPING:

MAPPING = {
    file_formats.MP4: VIDEO,
    file_formats.WEBM: VIDEO,
    file_formats.MP3: AUDIO,
    file_formats.PDF: DOCUMENT,
    file_formats.EPUB: DOCUMENT,
    file_formats.PERSEUS: EXERCISE,
    file_formats.HTML5: HTML5,
    file_formats.H5P: H5P,
    file_formats.ZIM: ZIM,
    file_formats.BLOOMPUB: DOCUMENT,
    file_formats.BLOOMD: DOCUMENT,
}
Target Spec Format

Create spec/constants-content_kinds.json with associated_formats metadata:

{
  "namedtuple": {
    "name": "Kind",
    "fields": ["id", "name"]
  },
  "constants": {
    "topic": {
      "name": "topic"
    },
    "video": {
      "name": "video",
      "associated_formats": ["mp4", "webm"]
    },
    "audio": {
      "name": "audio",
      "associated_formats": ["mp3"]
    },
    "exercise": {
      "name": "exercise",
      "associated_formats": ["perseus"]
    },
    "document": {
      "name": "document",
      "associated_formats": ["pdf", "epub", "bloompub", "bloomd"]
    },
    "html5": {
      "name": "html5",
      "associated_formats": ["zip"]
    },
    "slideshow": {
      "name": "slideshow"
    },
    "h5p": {
      "name": "h5p",
      "associated_formats": ["h5p"]
    },
    "zim": {
      "name": "zim",
      "associated_formats": ["zim"]
    },
    "quiz": {
      "name": "quiz"
    }
  }
}

Note: associated_formats is metadata (not a namedtuple field) used to auto-generate the MAPPING dict.

Generation Script Enhancement

Update scripts/generate_from_specs.py:

  1. Detect metadata fields (fields not in namedtuple definition)
  2. Generate MAPPING dict from associated_formats:
    • Import file_formats module
    • Create dict mapping format constants to kind constants
    • Python: MAPPING = {file_formats.MP4: VIDEO, ...}
    • JavaScript: export const KindsMapping = { mp4: "video", ... }
Generated Output Example

Python (le_utils/constants/content_kinds.py):

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

from le_utils.constants import file_formats

class Kind(namedtuple("Kind", ["id", "name"])):
    pass

TOPIC = "topic"
VIDEO = "video"
AUDIO = "audio"
# ...

choices = (
    (TOPIC, "Topic"),
    (VIDEO, "Video"),
    # ...
)

KINDLIST = [
    Kind(id="topic", name="topic"),
    Kind(id="video", name="video"),
    # ...
]

# File Format to Content Kind mapping (generated from associated_formats metadata)
MAPPING = {
    file_formats.MP4: VIDEO,
    file_formats.WEBM: VIDEO,
    file_formats.MP3: AUDIO,
    file_formats.PDF: DOCUMENT,
    file_formats.EPUB: DOCUMENT,
    file_formats.PERSEUS: EXERCISE,
    file_formats.HTML5: HTML5,
    file_formats.H5P: H5P,
    file_formats.ZIM: ZIM,
    file_formats.BLOOMPUB: DOCUMENT,
    file_formats.BLOOMD: DOCUMENT,
}

JavaScript (js/ContentKinds.js):

// Generated by scripts/generate_from_specs.py

export default {
    TOPIC: "topic",
    VIDEO: "video",
    AUDIO: "audio",
    // ...
};

export const KindsList = [
    { id: "topic", name: "topic" },
    { id: "video", name: "video" },
    // ...
];

export const KindsMap = new Map(
    KindsList.map(kind => [kind.id, kind])
);

// Format to Kind mapping
export const KindsMapping = {
    mp4: "video",
    webm: "video",
    mp3: "audio",
    pdf: "document",
    epub: "document",
    perseus: "exercise",
    zip: "html5",
    h5p: "h5p",
    zim: "zim",
    bloompub: "document",
    bloomd: "document",
};
Testing Updates

File: tests/test_kinds.py

Update to test against spec:

spec_path = os.path.join(os.path.dirname(__file__), "..", "spec", "constants-content_kinds.json")
with open(spec_path) as f:
    spec = json.load(f)
    kindlookup = spec["constants"]

# Verify MAPPING is generated correctly from associated_formats
How to Run Tests
pytest tests/test_kinds.py -v
pytest tests/ -v
Acceptance Criteria
  • spec/constants-content_kinds.json created with associated_formats metadata
  • scripts/generate_from_specs.py enhanced to generate MAPPING from metadata
  • make build successfully generates Python and JavaScript files
  • Generated le_utils/constants/content_kinds.py has:
    • Kind namedtuple
    • Uppercase constants (TOPIC, VIDEO, etc.)
    • choices tuple
    • KINDLIST with Kind namedtuples
    • Auto-generated MAPPING dict from associated_formats
  • Generated js/ContentKinds.js has:
    • Default export with constants
    • KindsList with full data
    • KindsMap for lookups
    • KindsMapping for format→kind lookups
  • tests/test_kinds.py updated to test against spec
  • All tests pass
  • resources/kindlookup.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 with le_utils/constants/content_kinds.py, le_utils/resources/kindlookup.json, scripts/generate_from_specs.py, and tests/test_kinds.py. Run pytest tests/test_kinds.py -v to understand current behavior, then review the acceptance criteria for spec metadata, generated Python and JavaScript outputs, and removal of the legacy resource. Done means make build succeeds and all tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, python
Domain
build-system, tooling
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.