learningequality / learningequality/le-utils
Migrate content_kinds.py to spec + code generation with metadata support
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.jsonat runtime - Manual Python constants (
TOPIC = "topic",VIDEO = "video", etc.) - Manual
MAPPINGdict 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:
- Detect metadata fields (fields not in namedtuple definition)
- Generate MAPPING dict from
associated_formats:- Import
file_formatsmodule - Create dict mapping format constants to kind constants
- Python:
MAPPING = {file_formats.MP4: VIDEO, ...} - JavaScript:
export const KindsMapping = { mp4: "video", ... }
- Import
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.jsoncreated withassociated_formatsmetadata -
scripts/generate_from_specs.pyenhanced to generate MAPPING from metadata -
make buildsuccessfully generates Python and JavaScript files - Generated
le_utils/constants/content_kinds.pyhas:- Kind namedtuple
- Uppercase constants (TOPIC, VIDEO, etc.)
-
choicestuple -
KINDLISTwith Kind namedtuples - Auto-generated
MAPPINGdict fromassociated_formats
- Generated
js/ContentKinds.jshas:- Default export with constants
-
KindsListwith full data -
KindsMapfor lookups -
KindsMappingfor format→kind lookups
-
tests/test_kinds.pyupdated to test against spec - All tests pass
-
resources/kindlookup.jsondeleted
Disclosure
🤖 This issue was written by Claude Code, under supervision, review and final edits by @rtibbles 🤖
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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