Skip to content

Feat: implement sdk - #1

Merged
Faraz32123 merged 15 commits into
masterfrom
feat/implement_sdk
Oct 6, 2026
Merged

Faraz32123 merged 15 commits into
masterfrom
feat/implement_sdk

Conversation

@Faraz32123

@Faraz32123 Faraz32123 commented Jun 24, 2026 •

Copy link
Copy Markdown
Collaborator
  • We have tagged our APIs in our openedx-platform implementation PRs (CMS / LMS) so that we can filter out the schema based on our tags.
  • In the SDK, regen_sdk.sh filters the schemas to only our tagged APIs using filter_schema.py, merges both LMS and CMS schemas into a single schema, and regenerates the SDK using openapi-python-client.
  • We have added a CI workflow in openedx-platform (Link) that automatically generates and commits the full OpenAPI schemas whenever a tagged API changes. The SDK's weekly CI then pulls those committed schemas and runs regen_sdk.sh to keep the SDK in sync, no running instance required.

Faraz32123 and others added 4 commits June 24, 2026 15:18
Adds the tools needed to generate the SDK from openedx-platform:
- filter_schema.py: filters the full OpenAPI schema to only paths tagged
  with openedx-platform-sdk, resolving all transitive $ref dependencies
- config.yml: openapi-python-client config (package/project name + include_tags)
- README.md: documents covered APIs, regeneration steps (curl + filter +
  openapi-python-client generate), and usage examples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generated with openapi-python-client from the filtered OpenAPI schema.
Covers 12 operations across 5 APIs:
- XBlock v1: create, retrieve, update, partial_update, destroy
- Authoring Grading v3: partial_update
- Course Details v3: retrieve, update
- Home v3: list, courses, libraries
- Home v4: courses (paginated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements OAuth2ClientCredentials for the OpenedX JWT auth flow:
- Fetches JWT via POST to {lms_url}/oauth2/access_token (client_credentials grant)
- Caches token and auto-refreshes 60s before expiry
- Returns AuthenticatedClient with prefix="JWT" (required by OpenedX)

Usage:
    auth = OAuth2ClientCredentials(lms_url, client_id, client_secret)
    with auth.get_client(studio_url) as client:
        result = v3_home_list.sync(client=client)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Automates the full SDK regeneration workflow:
1. Optional branch checkout in openedx-platform
2. Download OpenAPI schema from running Studio (STUDIO_URL env var)
3. Filter schema to openedx-platform-sdk tagged paths via filter_schema.py
4. Run openapi-python-client update (or generate on first run)

Usage:
    ./regen_sdk.sh                              # uses current platform branch
    ./regen_sdk.sh feat/axim-api_improvements   # checkout branch first

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 'Testing Locally' section to README: install, create OAuth2 app
    in LMS admin (user must be linked for JWT issuance), test script
- Fix studio_url in auth example to include /api/contentstore prefix
- Make regen_sdk.sh portable: PLATFORM_DIR env var for custom platform
    path, clear error if branch requested but repo not found, STUDIO_URL
    error message if Studio unreachable
- Rename v3_home_list → v3_home_retrieve: fixed HomeViewSet list action
    schema to return a single StudioHome object (not array) by overriding
    _is_list_view in a custom AutoSchema; generator renamed the module
    accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Faraz32123
Faraz32123 force-pushed the feat/implement_sdk branch from fd17006 to ad6a6ef Compare June 24, 2026 11:49
Faraz32123 and others added 2 commits July 3, 2026 16:06
…and generator bug fixes

Regenerated from updated openedx-platform schemas. Three platform-side
fixes drive this regen (see companion commit on add_changes_wrt_sdk):

  - Grading: grade_cutoffs, grace_period, minimum_grade_credit are now
    typed fields on AuthoringGradingCourseGradingV0 and
    PatchedauthoringGradingCourseGradingV0. Callers no longer need to
    pass these via additional_properties["key"] = value workarounds.
    New models: AuthoringGradingGracePeriodV0,
    *GradeCutoffs (additionalProperties wrapper for the dict field).

  - v4 Home: operation ID changed from v4_home_courses_list to
    v4_home_courses_retrieve (the schema now correctly describes a
    paginated object, not an array). New model PaginatedV4HomeCoursesResponse
    with count, num_pages, current_page, start, next_, previous, results.

  - Course details: certificate_available_date is now nullable in the
    schema; CourseDetails model updated accordingly.

Generator bug fixes baked into regen_sdk.sh (applied after every regen):

  - Bug 1: v3_course_details_update.py uses Unset in type annotations
    but the generator only imports UNSET. Fixed with a sed post-step.

  - Bug 2: _get_kwargs emits three identical isinstance(body, X) blocks
    for json / form / multipart — the multipart block always wins and
    breaks nested-dict payloads. Removed data/multipart blocks, kept
    only the JSON block. Affects: v1_xblock_{create,update,partial_update},
    v3_authoring_grading_partial_update, v3_course_details_update.

  - Bug 3: DictField wrapper models (e.g. GradeCutoffs) have their
    to_dict() called unconditionally in parent model's to_dict(), but
    users naturally pass plain Python dicts. Fixed with an isinstance
    guard: call .to_dict() only when the value is not already a dict.

auth.py: set Accept: application/json as a default header in get_client().
XBlock retrieve returns 406 without it — httpx sends Accept: */* by
default which the view rejects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…groups

Covers all five API groups exposed by the SDK with working, copy-paste
ready code snippets:

  - Home v3: retrieve (studio name + courses + libraries), courses-only,
    libraries-only endpoints
  - Home v4: paginated courses with count / num_pages / results traversal
  - Course Details v3: retrieve and PUT round-trip (retrieve → mutate →
    update → restore pattern)
  - Authoring Grading v3: PATCH with typed grade_cutoffs, grace_period,
    and minimum_grade_credit fields (no more additional_properties hack)
  - XBlock v1: retrieve, create, partial_update (rename), destroy —
    including a single end-to-end lifecycle example

Also updates README to:
  - Use typed SDK calls (v3_home_retrieve, v4_home_courses_retrieve) in
    the Authentication, Usage, and Testing Locally sections instead of
    the raw client.get_httpx_client().request() call
  - Add a link to docs/testing-sdk-apis.rst from the Testing Locally
    section for readers who want the full per-API examples

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the SDK to cover the LMS Enrollment v2 API by merging an LMS
drf-spectacular schema into the Studio schema during regen_sdk.sh.

SDK changes:
- regen_sdk.sh: download LMS schema from /lms-api/schema/, pass --merge to
  filter_schema.py; add Bug 5 fix (plain-list response for enrollment_allowed)
- filter_schema.py: add merge_schema(), fix_path_parameters() to strip
  spurious path params that cause the generator to skip endpoints; add
  --merge CLI flag
- New API modules: v2_enrollment_*, v2_course_retrieve, v2_roles_retrieve,
  v2_enrollments_list (11 files)
- New models: CourseEnrollment, EnrollmentCourse, CourseEnrollmentAllowed,
  and supporting paginated/response models (13 files)
- lms_schema.yml: cached LMS enrollment schema

Bug fixes applied at regen time:
- Bug 4: null-safe datetime parsing in EnrollmentCourse.from_dict()
- Bug 5: plain-list normalisation in PaginatedCourseEnrollmentAllowedList

Docs:
- README.md: add Enrollment v2 to covered APIs table, update regen steps
- docs/testing-sdk-apis.rst: update enrollment section with correct module
  names, base URL (/api/enrollment), and accurate LMS schema note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@feanil feanil left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a quick look at the generation script and approach.

Is all the python code generated or is some of it manually written?

Comment thread docs/testing-sdk-apis.rst
Comment thread regen_sdk.sh Outdated
Comment thread regen_sdk.sh Outdated
@Faraz32123

Copy link
Copy Markdown
Collaborator Author

Took a quick look at the generation script and approach.

Is all the python code generated or is some of it manually written?

Hi @feanil, Most of it is auto-generated by openapi-python-client from the filtered OpenAPI schema. The hand-written parts are:

  • openedx_platform_sdk/auth.py - OAuth2ClientCredentialsclass for fetching/caching JWT tokens via OAuth2
  • filter_schema.py - schema filtering + LMS schema merging logic
  • regen_sdk.sh- regeneration automation
    Everything under openedx_platform_sdk/api/ and openedx_platform_sdk/models/ is generated, as are client.py, types.py and __init__.py.

@Faraz32123
Faraz32123 requested a review from feanil September 9, 2026 11:49
Comment thread regen_sdk.sh Outdated
Comment thread regen_sdk.sh
Comment thread regen_sdk.sh Outdated
address comments on regen_sdk.sh and regenerate_sdk workflow
- explicit schema source modes (no implicit defaults)
- rename schema.yml to cms_schema.yml
- remove branch checkout
- Tuesday schedule, as openedx-platform workflow will run on Monday, reviewers will have ample time to review and merge the PR.
@Faraz32123
Faraz32123 requested a review from feanil September 10, 2026 13:15
Comment thread regen_sdk.sh Outdated
Comment thread regen_sdk.sh Outdated
Comment thread regen_sdk.sh Outdated
Comment thread postprocess_sdk.py Outdated
Comment thread postprocess_sdk.py Outdated
Comment thread .github/workflows/regenerate_sdk.yml Outdated
Comment thread .github/workflows/regenerate_sdk.yml Outdated
Comment thread .github/workflows/regenerate_sdk.yml Outdated
Comment thread .github/workflows/regenerate_sdk.yml Outdated
Comment thread .github/workflows/regenerate_sdk.yml Outdated
- regen_sdk.sh now takes PLATFORM_DIR or the schema URLs explicitly instead
  of inferring them, and prefixes the schema filenames per service
- moved the auth.py round-trip out of `sed -i ''` (BSD-only, failed on Linux
  after the tree was already removed) and into postprocess_sdk.py
- post-processing fails when a step patches nothing, and schema merges report
  their collisions, so a schema change can't quietly invalidate a fix
- the three platform schema workarounds name their exit path:
  openedx/openedx-platform#39120 for the two response-shape bugs,
  openedx/openedx-platform#39121 for the spurious path parameter
- pinned the workflow actions to SHAs and recorded the platform revision from
  the platform checkout rather than github.sha
- dropped the two unenroll request body models the API never accepts
@Faraz32123
Faraz32123 requested a review from feanil September 17, 2026 15:58
openedx/openedx-platform#39025 writes the generated schemas to
docs/lms-openapi.yaml and docs/cms-openapi.yaml instead of the repo root, so
follow the sparse-checkout, the CI env vars, and the PLATFORM_DIR copy. The
SDK's own local copies keep their cms_schema.yml / lms_schema.yml names.
Comment thread .github/workflows/regenerate_sdk.yml Outdated
Comment thread postprocess_sdk.py Outdated
Poetry installed the dev group into its own virtualenv, so regen_sdk.sh ran in
a plain shell with nothing on PATH and died on `import yaml` before reaching
the generator. pyproject.toml is PEP 621 now, uv.lock is committed, and the
script runs its tools through `uv run`.

Two things would have broken the first scheduled run. Ruff exits 1 on the
UP042s the generator emits and can't auto-fix, so the workflow never got as
far as opening a PR that rule is ignored now. And create-pull-request
commits everything untracked when add-paths is unset, so it would have carried
the platform/ checkout into the PR; scoped the commit and ignored the dir.

The unenroll fix deleted the two body model files unconditionally while the
__all__ removal only matched one exact indentation, and a shared counter let
one half cover for the other. Each half is tracked separately now.

Also moved the README to uv and added the studio_url its example was missing.
@Faraz32123

Copy link
Copy Markdown
Collaborator Author

Two follow-ups.

Found a workflow bug: create-pull-request commits everything untracked when add-paths is unset, and platform/ (the sparse checkout from step 2) wasn't gitignored — so the first run would have committed openedx-platform's docs into this repo. Scoped the commit and ignored the directory.

Also #39120 merged this morning. Fixes 4 and 5 stay until the SDK is regenerated from a platform revision that includes it; then they'll patch nothing, fail the run by design, and that's the signal to delete them.

@Faraz32123
Faraz32123 requested a review from feanil September 22, 2026 16:12

@feanil feanil left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small fixes and then I think this is good to go.

Comment thread .github/workflows/regenerate_sdk.yml Outdated
# and --frozen fails the run if uv.lock has drifted from it. Later steps
# invoke the tools through `uv run`, so they resolve inside this
# environment instead of relying on what happens to be on PATH.
run: uv sync --frozen

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--frozen installs from uv.lock without checking it against pyproject.toml, so the generator pin isn't enforced. Bump the pin in pyproject.toml, leave the lock alone, and this step still passes:

$ grep openapi-python-client pyproject.toml
    "openapi-python-client==0.28.0",
$ uv sync --frozen
EXIT=0
$ uv run --frozen openapi-python-client --version
openapi-python-client version: 0.29.0

Use uv sync --locked, which fails with "The lockfile at uv.lock needs to be updated". It exits 0 on this branch as-is.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the changes.

Comment thread regen_sdk.sh Outdated

# ── 2. Filter and merge both schemas ──────────────────────────────────────────
echo "→ Filtering and merging schemas for tag '$SDK_TAG'..."
uv run python "$FILTER_SCRIPT" "$CMS_SCHEMA_FILE" "$FILTERED_SCHEMA_FILE" "$SDK_TAG" --merge "$LMS_SCHEMA_FILE"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uv run resolves the project from the current directory, not from $SCRIPT_DIR, so running this script by path from anywhere else is last round's error again:

$ cd /tmp/elsewhere
$ CMS_SCHEMA_FILE=... LMS_SCHEMA_FILE=... /path/to/regen_sdk.sh
→ Filtering and merging schemas for tag 'openedx-platform-sdk'...
  File "/path/to/filter_schema.py", line 14, in <module>
    import yaml
ModuleNotFoundError: No module named 'yaml'

A bare uv run also re-resolves and rewrites uv.lock in place, so it can install a different generator than the sync step did.

Use uv run --locked --project "$SCRIPT_DIR" on all three calls.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

--frozen installs uv.lock without checking it against pyproject.toml, so the
generator pin was never enforced: setting it to 0.28.0 still installed 0.29.0
and exited 0. Use --locked, which fails when the two disagree.

uv run resolves the project from the working directory, so running the script
by path from elsewhere picked up whatever environment the caller was in rather
than the SDK's. A bare uv run also re-resolves and rewrites uv.lock, which can
install a different generator than the sync step did. Pass --locked and
--project on each call.

@feanil feanil left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: README.md:33 still says uv sync --frozen for the regeneration setup. Worth matching the workflow.

I think this is good to merge after the drf-yasg drop PR lands.

@Faraz32123 Faraz32123 changed the title Feat/implement sdk Feat: implement sdk Oct 6, 2026
@Faraz32123
Faraz32123 merged commit bb759ff into master Oct 6, 2026
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.

2 participants