Metadata-Version: 2.4
Name: python-moodle
Version: 1.0.0
Summary: Python CLI and library to automate Moodle via web sessions, CAS/SSO, course management and SCORM uploads
Author-email: Ernesto Serrano <info@ernesto.es>
Project-URL: Homepage, https://github.com/erseco/python-moodle
Project-URL: Documentation, https://erseco.github.io/python-moodle/
Project-URL: Repository, https://github.com/erseco/python-moodle
Project-URL: Issues, https://github.com/erseco/python-moodle/issues
Project-URL: Changelog, https://github.com/erseco/python-moodle/releases
Keywords: moodle,lms,cli,automation,scorm,cas,sso,education,elearning,web-scraping
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Education
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests
Requires-Dist: beautifulsoup4
Requires-Dist: lxml
Requires-Dist: python-dotenv
Requires-Dist: typer[all]
Requires-Dist: rich
Requires-Dist: pyyaml
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pytest-xdist; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Requires-Dist: flake8-docstrings; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: mkdocstrings[python]; extra == "dev"
Requires-Dist: pymdown-extensions; extra == "dev"
Requires-Dist: zensical; python_version >= "3.10" and extra == "dev"
Dynamic: license-file

<p align="center">
  <img src="https://raw.githubusercontent.com/erseco/python-moodle/main/docs/images/py-moodle-icon.png" alt="python-moodle logo" width="128">
</p>

# python-moodle

[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/erseco/python-moodle/blob/main/LICENSE)
[![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![CI](https://github.com/erseco/python-moodle/actions/workflows/ci.yml/badge.svg)](https://github.com/erseco/python-moodle/actions/workflows/ci.yml)
[![PyPI downloads](https://img.shields.io/pypi/dm/python-moodle)](https://pypi.org/project/python-moodle/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![GitHub repository](https://img.shields.io/badge/github-repository-blue)](https://github.com/erseco/python-moodle)

> **Python CLI and library for automating Moodle through real web sessions, with CAS/SSO support and no custom plugins required.**

## Why python-moodle?

- **No API token required** — works with standard Moodle login and CAS/SSO
- **Works with web sessions** — simulates a real user browser session, no Moodle plugins needed
- **CLI for admins** and **library for automation scripts**
- **Handles courses, sections, labels, folders, assignments and SCORM** out of the box
- **Tested against Moodle 4.x and 5.x** on Python 3.8–3.13

### python-moodle vs. Moodle webservice wrappers

| Capability                    | python-moodle | Moodle webservice wrappers |
| ----------------------------- | :-----------: | :------------------------: |
| No custom plugin required     | ✅ Yes        | Usually yes/no             |
| Works with real web sessions  | ✅ Yes        | ❌ No                      |
| CAS/SSO support               | ✅ Yes        | Usually limited            |
| CLI included                  | ✅ Yes        | Often no                   |
| SCORM/course admin workflows  | ✅ Yes        | Varies                     |

---

## 3-Minute Quickstart

```bash
pip install python-moodle
cp .env.example .env
# Edit .env with your Moodle URL and credentials
python-moodle courses list
```

### Typical automation tasks

| Task                          | Command / Use case                            |
| ----------------------------- | --------------------------------------------- |
| Bulk course creation          | `python-moodle courses create`                    |
| SCORM upload pipelines        | `python-moodle modules add scorm`                 |
| Content migration             | Script with `list_courses` + `create_course`  |
| Moodle smoke tests in CI      | `pytest` + `MoodleSession` fixture            |
| Admin scripting across instances | `--env` flag with multiple `.env` profiles |

---

!!! warning "Experimental"
    This library is under active development. Use a test Moodle instance and back up data before running commands that create, modify, or delete content.

`python-moodle` allows you to automate tedious Moodle tasks—like creating courses, uploading content, and managing modules—directly from your terminal or in your Python scripts. It works by simulating a real user's web session, so it doesn't require API tokens or special Moodle plugins.

---

## Features

-   **Manage Moodle entities**: Courses, sections, users, and modules from the command line.
-   **Rich module support**: Includes built-in support for Folders, Labels, Assignments, and SCORM packages.
-   **Session-based**: Works with standard Moodle web sessions, avoiding the need for web service tokens.
-   **Authentication**: Supports standard Moodle login and SSO/CAS authentication.
-   **Dual Use**: Can be used as a powerful CLI or imported as a library into your own Python projects.
-   **High-level `MoodleClient` facade**: A discoverable, object-oriented API (`moodle.courses.list()`, `moodle.scorm.add(...)`) over the function-based library, plus opt-in typed domain models (`Course`, `User`, `Folder`, ...) for IDE autocompletion.
-   **Diagnostics**: `python-moodle doctor run` checks base URL reachability, login, sesskey/webservice token availability, and more in one command.
-   **Idempotent automation**: `courses ensure` creates a course only if it doesn't already exist, safe to run repeatedly in CI/onboarding scripts.
-   **Safe previews**: `--dry-run` on `courses create`, `courses delete`, and `modules add scorm` shows the planned action without touching Moodle.
-   **Scripting-friendly output**: `--output table|json|yaml|csv`, plus `--quiet`, `--no-color`, `--verbose`, and `--debug` (with automatic secret redaction) for CI pipelines and shell automation.
-   **Extensible**: Designed to be easily extended with new modules and commands. See `AGENTS.md`.
-   **English-only codebase**: For clear, global collaboration.

---

## Moodle Compatibility

`python-moodle` interacts with Moodle through authenticated web sessions, HTML forms, and page parsing. To make those flows more resilient across Moodle releases, the library now centralizes version-sensitive logic in `py_moodle.compat`.

-   **Version detection** happens during login/session initialization. The library first tries `core_webservice_get_site_info` when a webservice token is available, then falls back to probing the dashboard HTML (`/my/`) for Moodle release metadata.
-   **Version-aware strategies** are grouped into compatibility ranges instead of scattering selectors throughout the codebase. The current built-in strategies cover legacy Moodle 3.x layouts and modern Moodle 4.x/5.x layouts.
-   **Feature probing remains in place** when version detection is not enough. Each strategy can try multiple selectors or form patterns before failing.
-   **Fragile flows should read selectors from the compatibility layer** so future Moodle HTML changes are isolated to one module.

At the moment, representative compatibility handling has been wired into login/session bootstrap, generic module form parsing, and folder page scraping. When a new Moodle release changes one of these flows, the recommended fix is to update `py_moodle.compat` and add a regression test for the new selector or workflow.

---

## Installation

You will need Python 3.8+ and `pip`.

### Install from PyPI (Recommended)

```bash
pip install python-moodle
```

### Install from Source

Clone the repository and install:

```bash
git clone https://github.com/erseco/python-moodle.git
cd python-moodle
pip install .
```

### Configure your environment

Copy the example `.env.example` file to `.env` and add your Moodle instance credentials.

```bash
cp .env.example .env
# Now, edit the .env file with your credentials
```

!!! danger
    The `.env` file stores real credentials. Keep it out of version control and share it with no one.

Your `.env` file should look like this:

```env
# Production environment credentials
MOODLE_PROD_URL=https://your.moodle.site
MOODLE_PROD_USERNAME=your_admin_user
MOODLE_PROD_PASSWORD=your_super_secret_password
# Optional: CAS SSO URL
# MOODLE_PROD_CAS_URL=https://cas.your-institution.org/cas
# Optional: Predefined webservice token (required for CAS)
# MOODLE_PROD_WS_TOKEN=your_webservice_token
```

Use the `--env` flag or the `MOODLE_ENV` variable to select the environment, e.g. `python-moodle --env prod courses list`.

> **Note**: For local development, you can quickly spin up a Moodle instance using the provided `docker-compose.yml`: `docker-compose up -d`.

If something is not working for a given environment, run `python-moodle --env prod doctor run` to check base URL reachability, login, sesskey/webservice token availability, and a few other diagnostics in one command (see the [Recipes](https://erseco.github.io/python-moodle/recipes/#diagnosing-a-broken-environment) page for details).

---

## CLI Usage

Once installed, all functionality is available through the `python-moodle` command (or its shorter alias `py-moodle`). Every command and subcommand includes detailed help with the `-h` or `--help` flag.

### Common Commands

Here are a few examples of common commands:

**List all available courses:**

```bash
python-moodle courses list
```

*Output:*

```
┏━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┓
┃ ID ┃ Shortname          ┃ Fullname           ┃ Category ┃ Visible ┃
┡━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━┩
│ 2  │ my-first-course    │ My first course    │ 1        │ 1       │
│ 4  │ my-second-course   │ My second course   │ 1        │ 1       │
└────┴────────────────────┴────────────────────┴──────────┴─────────┘
```

**Show the structure of a single course:**

```bash
python-moodle courses show 2
```

**Create a new course:**

```bash
python-moodle courses create --fullname "My New Automated Course" --shortname "auto-course-01"
```

**Add a label to a course section:**

```bash
python-moodle modules add label --course-id 2 --section-id 1 --name "Welcome" --intro "<h1>Welcome to the course!</h1>"
```

**Upload a SCORM package to a course:**

```bash
python-moodle modules add scorm --course-id 2 --section-id 1 --name "My SCORM Package" --path "path/to/your/scorm.zip"
```

---

## Library Usage (Automation Scripting)

You can also import `python-moodle`'s functions into your own Python scripts to automate complex workflows. The `example_script.py` file provides a comprehensive tutorial.

### Quick Example

```python
from py_moodle import MoodleSession
from py_moodle.course import list_courses

# Credentials are loaded automatically from your .env file
ms = MoodleSession.get()
courses = list_courses(ms.session, ms.settings.url, token=ms.token)
for course in courses:
    print(course["id"], course["fullname"])
```

### The `MoodleClient` Facade

For scripts that call more than one or two functions, `MoodleClient` collapses
the `session`/`base_url`/`token`/`sesskey` boilerplate into a single object,
with a discoverable `moodle.courses` / `moodle.sections` / `moodle.scorm` /
... API:

```python
from py_moodle import MoodleClient

with MoodleClient.from_env("prod") as moodle:
    courses = moodle.courses.list()
    course = moodle.courses.create(fullname="My course", shortname="my-course")
    moodle.scorm.add(course_id=course["id"], section_id=1, path="package.zip")
```

See the [Client API Reference](https://erseco.github.io/python-moodle/api/client/)
for the full list of resource namespaces.

### How the Example Script Works

The script is a self-contained demonstration that:

1.  Logs into Moodle using the credentials from your `.env` file.
2.  Creates a new, temporary course.
3.  Populates the course with sections, labels, assignments, and a SCORM package.
4.  Creates a folder and uploads multiple files to it.
5.  Prints a summary of the final course structure.
6.  **Automatically cleans up and deletes the course and all its contents.**

### Running the Example

Make sure you have a valid `.env` file and have installed the dependencies. Then, simply run:

```bash
python example_script.py
```

This script is the best starting point for understanding how to use the library's functions for your own automation needs.

---

## Testing

The project uses `pytest` and provides a `Makefile` with convenient targets.

Run the fast smoke test suite (no Moodle service required):

```bash
make test-unit
```

Run the Docker-backed integration suite against the local environment:

```bash
make test-local
```

Run tests against the staging environment:

```bash
make test-staging
```

Run all configured environments:

```bash
make test
```

GitHub Actions automatically runs:

- linting on Python 3.13
- smoke tests on Python 3.9 through 3.13
- Docker-backed integration tests on representative Python/Moodle combinations:
  - Python 3.9 with Moodle 4.5.5
  - Python 3.13 with Moodle 5.0.1
  - Python 3.13 with Moodle 5.1.5

## Development

Use the Makefile to format code, run linters, or build the documentation:

```bash
make format   # run black and isort
make lint     # run static analysis
make docs     # build the MkDocs site
```

---

## Documentation

Documentation is generated with [Zensical](https://zensical.org/) using the Material theme and published automatically to the `gh-pages` branch.
Every push to `main` builds the API reference and CLI guide from the source code and makes it available via GitHub Pages.

To build the documentation locally (requires Python 3.10+):

```bash
pip install zensical 'mkdocstrings[python]'
zensical build
```

The rendered site will be available under the `site/` directory.

---

## Contribution

Contributions are welcome! Please follow the guidelines outlined in **[AGENTS.md]**. Key principles include:

-   All code, comments, and documentation must be in **English**.
-   Code must be formatted with `black` and linted with `flake8`.
-   Docstrings must use the Google style; `flake8-docstrings` is configured for this convention.
-   The CLI should be a thin layer over the core library functions.
-   All new features must be accompanied by tests.

---

## License

This project is licensed under the [MIT License](./LICENSE).
