Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/api/ensure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Ensure

Idempotent, ensure-style provisioning helpers for sections and content
modules. Each helper looks up an existing object by its human-readable natural
key and only creates a new one when nothing matches, mirroring
[`ensure_course`](course.md).

::: py_moodle.ensure
51 changes: 51 additions & 0 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,57 @@ py-moodle courses ensure \
`--update` only ever touches `fullname` and category membership; it never
overwrites the course summary, visibility, or its sections/modules.

## Idempotent content provisioning

The `py_moodle.ensure` module extends the same create-or-reuse pattern to
sections and content modules, so a provisioning script can be re-run safely
without creating duplicate sections, labels, resources, or folders. Each
helper keys on a human-readable natural key: sections on their `name`, and
modules on the `(name, modname)` pair within the course.

```python
from py_moodle import MoodleSession
from py_moodle.course import create_or_update_course
from py_moodle.ensure import ensure_label, ensure_section

ms = MoodleSession.get()
session, url, sesskey = ms.session, ms.settings.url, ms.sesskey

# Create the course if missing, or bring its fullname/category in line.
course = create_or_update_course(
session,
url,
sesskey,
shortname="ci-smoke-test",
fullname="CI Smoke Test",
category_id=1,
).course

# Ensure a named section exists (created and renamed only when missing).
section = ensure_section(
session, url, sesskey, course["id"], name="Welcome"
)

# Ensure a welcome label exists in that section; a second run reuses it.
label = ensure_label(
session,
url,
sesskey,
course["id"],
section.section["id"],
name="Welcome message",
html="<p>Welcome to the course!</p>",
)

print(section.status, label.status) # e.g. "created created", then "reused reused"
```

`ensure_resource` and `ensure_folder` work the same way, forwarding their
entity-specific arguments (`file_path`, `files_itemid`, ...) to the underlying
`add_*` primitives only when a matching module does not already exist. Because
the idempotency keys are human names, renaming a section or module in Moodle
makes the next run treat it as absent and re-create it -- pick stable names.

## Get IDE-friendly typed models from raw dicts (optional)

The library functions keep returning plain `dict`/`list[dict]` values, but
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ nav:
- Course: api/course.md
- Category: api/category.md
- Section: api/section.md
- Ensure: api/ensure.md
- Content Modules:
- Module: api/module.md
- Label: api/label.md
Expand Down
50 changes: 50 additions & 0 deletions src/py_moodle/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,55 @@ def ensure_course(
return EnsureCourseResult(status="updated", course=updated_course)


def create_or_update_course(
session: requests.Session,
base_url: str,
sesskey: str,
*,
shortname: str,
fullname: str,
category_id: int,
token: str | None = None,
**create_kwargs: Any,
) -> EnsureCourseResult:
"""Create a course, or update it if one with the shortname already exists.

Convenience wrapper over :func:`ensure_course` with ``update=True``: if a
course with ``shortname`` exists it is updated in place (differing
``fullname``/``category_id`` fields are applied) instead of reporting a
conflict; otherwise a new course is created.

Args:
session: Authenticated requests session.
base_url: Base URL of the Moodle instance.
sesskey: Session key for AJAX/form calls.
shortname: Unique shortname used to look up an existing course.
fullname: Desired full name of the course.
category_id: Desired category ID of the course.
token: Optional webservice token, forwarded to ``list_courses``.
**create_kwargs: Extra keyword arguments forwarded to
``create_course`` when a new course must be created.

Returns:
EnsureCourseResult: Typed result with ``status`` of ``"created"``,
``"reused"``, or ``"updated"``.

Raises:
MoodleCourseError: If listing, creating, or updating fails.
"""
return ensure_course(
session,
base_url,
sesskey,
shortname=shortname,
fullname=fullname,
category_id=category_id,
token=token,
update=True,
**create_kwargs,
)


def delete_course(
session: requests.Session,
base_url: str,
Expand Down Expand Up @@ -890,6 +939,7 @@ def list_sections(course_contents: List[Dict[str, Any]]) -> List[Dict[str, Any]]
"list_courses",
"create_course",
"ensure_course",
"create_or_update_course",
"update_course_basic",
"delete_course",
"get_course",
Expand Down
Loading