Skip to content

Extend the ensure-style API: sections + content modules + create_or_update_course#80

Merged
erseco merged 1 commit into
mainfrom
issue-77-ensure-api
Jul 8, 2026
Merged

Extend the ensure-style API: sections + content modules + create_or_update_course#80
erseco merged 1 commit into
mainfrom
issue-77-ensure-api

Conversation

@erseco

@erseco erseco commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Extends the idempotent ensure-style API beyond ensure_course (roadmap Subtasks 18-20). Adds a new py_moodle.ensure module with create-or-reuse helpers for content modules and sections, plus a create_or_update_course convenience 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) and ensure_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 calls create_fn; otherwise create_fn() is invoked exactly once and must return the cmid.
  • ensure_label / ensure_resource / ensure_folder thin wrappers binding modname ("label"/"resource"/"folder") and forwarding entity-specific kwargs to the matching add_* via create_fn closures.
  • EnsureSectionResult(status, section) and ensure_section(session, base_url, sesskey, course_id, *, name): reuse a same-named section, else create_section then rename it to name.
  • create_or_update_course(...) in course.py delegating to ensure_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 ensure module yet):

$ .venv/bin/pytest tests/unit/test_ensure_api.py -q
ImportError while importing test module '.../tests/unit/test_ensure_api.py'.
E   ImportError: cannot import name 'ensure' from 'py_moodle' (.../src/py_moodle/__init__.py)
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!

Green (after implementing ensure.py + create_or_update_course):

$ .venv/bin/pytest tests/unit/test_ensure_api.py -q
..........  [100%]
10 passed

$ .venv/bin/pytest tests/unit
226 passed, 1 warning in 0.54s

Lint (all green): black --check src tests, isort --check-only src tests, flake8.

Test plan

  • ensure_module returns reused and does NOT call create_fn when a module with the same (name, modname) exists.
  • ensure_module returns created and calls create_fn exactly once when absent.
  • Same name but different modname is treated as absent (-> created).
  • ensure_label / ensure_resource / ensure_folder delegate with the right modname and forward their kwargs (html/file_path/files_itemid, visible, ...).
  • ensure_label reuses an existing same-named label without calling add_label.
  • ensure_section reuses a same-named section (no create/rename) and otherwise creates + renames.
  • create_or_update_course delegates to ensure_course(update=True) and forwards extra kwargs.
  • Full tests/unit suite: 226 passed. Lint green.

Backwards compatibility

Additive only. No changes to add_*, create_section, or ensure_course signatures. New names live in py_moodle.ensure and create_or_update_course in py_moodle.course. py_moodle/__init__.py was intentionally left untouched because it does not re-export ensure_course.

Documentation

  • New docs/api/ensure.md (mkdocstrings ::: py_moodle.ensure) and an Ensure: api/ensure.md nav entry under Course Management in mkdocs.yml.
  • New "Idempotent content provisioning" recipe in 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 (added create_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_section uses a minimal private _rename_section that calls Moodle's core_update_inplace_editable (itemtype="sectionname", component="format_topics"), mirroring rename_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. Both create_section and the rename are fully mocked in the unit tests.

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.
@erseco erseco merged commit e810984 into main Jul 8, 2026
13 checks passed
@erseco erseco deleted the issue-77-ensure-api branch July 8, 2026 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend the ensure-style API: ensure_section/label/resource/folder + create_or_update_course (Subtasks 18-20)

1 participant