Extend the ensure-style API: sections + content modules + create_or_update_course#80
Merged
Conversation
Extend the idempotent ensure_course pattern beyond courses: - New src/py_moodle/ensure.py with EnsureModuleResult/EnsureSectionResult dataclasses and ensure_module/ensure_label/ensure_resource/ensure_folder/ ensure_section helpers. ensure_module keys idempotency on (name, modname) within the course; ensure_section keys on the section name. - create_or_update_course convenience wrapper in course.py delegating to ensure_course(update=True). - Fully mocked unit tests covering reuse vs create, modname discrimination, wrapper delegation/kwarg forwarding, section create+rename, and the create_or_update_course delegation. - docs/api/ensure.md + mkdocs nav entry and an idempotent content provisioning recipe. Additive only: no existing signatures change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends the idempotent ensure-style API beyond
ensure_course(roadmap Subtasks 18-20). Adds a newpy_moodle.ensuremodule with create-or-reuse helpers for content modules and sections, plus acreate_or_update_courseconvenience wrapper. Provisioning scripts no longer need to hand-roll "create it if it isn't there" logic for sections, labels, resources, and folders.Linked issue
Closes #77
Specification (SDD)
Implements the contract in issue #77 sections 1-9:
EnsureModuleResult(status, cmid, module)andensure_module(session, base_url, sesskey, course_id, section_id, *, name, modname, create_fn)keyed on(name, modname)within the course. Reuse returns the existing module and never callscreate_fn; otherwisecreate_fn()is invoked exactly once and must return thecmid.ensure_label/ensure_resource/ensure_folderthin wrappers bindingmodname("label"/"resource"/"folder") and forwarding entity-specific kwargs to the matchingadd_*viacreate_fnclosures.EnsureSectionResult(status, section)andensure_section(session, base_url, sesskey, course_id, *, name): reuse a same-named section, elsecreate_sectionthen rename it toname.create_or_update_course(...)incourse.pydelegating toensure_course(..., update=True).Lookups use
get_course_with_sections_and_modules(imported lazily to avoid import cycles, mirroring existing code). Additive only; no existing signatures changed.TDD evidence
Tests were written first and confirmed to fail before implementation.
Red (no
ensuremodule yet):Green (after implementing
ensure.py+create_or_update_course):Lint (all green):
black --check src tests,isort --check-only src tests,flake8.Test plan
ensure_modulereturnsreusedand does NOT callcreate_fnwhen a module with the same(name, modname)exists.ensure_modulereturnscreatedand callscreate_fnexactly once when absent.modnameis treated as absent (-> created).ensure_label/ensure_resource/ensure_folderdelegate with the rightmodnameand forward their kwargs (html/file_path/files_itemid,visible, ...).ensure_labelreuses an existing same-named label without callingadd_label.ensure_sectionreuses a same-named section (no create/rename) and otherwise creates + renames.create_or_update_coursedelegates toensure_course(update=True)and forwards extra kwargs.tests/unitsuite: 226 passed. Lint green.Backwards compatibility
Additive only. No changes to
add_*,create_section, orensure_coursesignatures. New names live inpy_moodle.ensureandcreate_or_update_courseinpy_moodle.course.py_moodle/__init__.pywas intentionally left untouched because it does not re-exportensure_course.Documentation
docs/api/ensure.md(mkdocstrings::: py_moodle.ensure) and anEnsure: api/ensure.mdnav entry under Course Management inmkdocs.yml.docs/recipes.md.Notes for reviewers
Files changed (all within the assigned ownership boundary):
src/py_moodle/ensure.py(new)src/py_moodle/course.py(addedcreate_or_update_course+__all__entry only)tests/unit/test_ensure_api.py(new)docs/api/ensure.md(new)mkdocs.yml(one nav entry)docs/recipes.md(one recipe section)Design note on section renaming: there is no existing section-name helper in the codebase, so
ensure_sectionuses a minimal private_rename_sectionthat calls Moodle'score_update_inplace_editable(itemtype="sectionname",component="format_topics"), mirroringrename_module_name. This targets the built-in topics/weeks formats (the default for courses created by this library); the limitation is documented in the helper's docstring. Bothcreate_sectionand the rename are fully mocked in the unit tests.