Skip to content

RFC: Auto-generate network layer from Webex OpenAPI specs to keep SDK in sync with the API #273

@adamweeks

Description

@adamweeks

Summary

Webex recently published all their public API specs at webex/webex-openapi-specs — 9 OpenAPI JSON files covering every Webex service family, updated nearly every day via an automated pipeline. We'd like to use those specs to auto-generate a thin network layer inside this SDK so it can never fall behind the Webex API again.

This is an RFC (Request for Comments). We have a full design spec and want community input before writing any code.


The problem today

The SDK's 22 API wrapper files are entirely hand-maintained. When Webex adds or changes a parameter, someone has to notice and manually update the code. In practice, that doesn't always happen quickly — or at all.

Known drift that already exists right now:

File Issue
events.py Missing serviceType parameter; resource enum values are stale (docs say "messages, memberships" — spec shows 8+ values)
rooms.py ECM folder linking (/room/linkedFolders) not in SDK
HDS (Hosted Data Services) endpoints not in SDK
BroadWorks, Cloud Calling, Contact Center, Device, UCM, Wholesale APIs have zero SDK coverage despite being in the specs

And the webex/webex-openapi-specs repo gets ~daily commits, so this gap only grows.


The proposed solution: a three-layer architecture

Rather than replacing the hand-written code, we'd add a generated layer underneath it:

┌──────────────────────────────────────────────────────┐
│  api/messages.py, api/rooms.py, ...   CUSTOM LAYER   │
│  Unchanged public API. All the Pythonic ergonomics:   │
│  pagination, type checking, file uploads, aliases     │
│  Calls into ↓                                         │
├──────────────────────────────────────────────────────┤
│  _generated/messaging.py, _generated/meeting.py, ... │
│              GENERATED NETWORK LAYER                  │
│  One function per OpenAPI operationId. Knows every    │
│  endpoint URL, method, and parameter. Regenerated     │
│  automatically when the specs change.                 │
│  Calls into ↓                                         │
├──────────────────────────────────────────────────────┤
│  restsession.py               UNCHANGED               │
│  Auth, rate limiting, RFC5988 pagination              │
└──────────────────────────────────────────────────────┘

What changes

  • A new generator/openapi_to_generated.py script reads the Webex OpenAPI JSON files and writes webexpythonsdk/_generated/*.py
  • Each generated file contains one function per API operation — it knows the endpoint URL, which HTTP method to use, and which parameters go where
  • The existing api/*.py classes delegate their HTTP calls into the generated layer instead of calling self._session directly
  • A scheduled GitHub Actions workflow fetches the latest specs daily and opens a PR if anything changed

What stays exactly the same

Every part of the SDK users interact with today is preserved:

  • All public method signatures, parameter names, and defaults
  • @generator_container reusable pagination
  • Runtime type checking via check_type()
  • Local file → multipart upload in messages.create()
  • AdaptiveCard object handling in messages.create()
  • **request_parameters escape hatch on every method
  • messages.edit alias, people.me(), rooms.get_meeting_info(), messages.list_direct() — all kept

What gets better immediately

  • New Webex parameters appear in the generated layer on the next daily sync and flow through **request_parameters to callers right away — no SDK change needed
  • New endpoints become accessible as soon as specs update
  • The daily sync PR gives maintainers a clear diff of what Webex changed

What a generated function looks like

# webexpythonsdk/_generated/messaging.py
# AUTO-GENERATED from webex-messaging.json — do not edit

def _compact(**kw):
    return {k: v for k, v in kw.items() if v is not None}

# GET /messages  →  paginated list
def list_messages(session, roomId, parentId=None, mentionedPeople=None,
                  before=None, beforeMessage=None, max=50, **extra):
    params = _compact(roomId=roomId, parentId=parentId,
                      mentionedPeople=mentionedPeople, before=before,
                      beforeMessage=beforeMessage, max=max, **extra)
    return session.get_items('messages', params=params)

# GET /events  →  'from' is a Python keyword, renamed to from_
def list_events(session, resource=None, type=None, actorId=None,
                from_=None, to=None, max=None, serviceType=None, **extra):
    params = _compact(resource=resource, type=type, actorId=actorId,
                      to=to, max=max, serviceType=serviceType, **extra)
    if from_ is not None:
        params['from'] = from_
    return session.get_items('events', params=params)

And the custom layer on top stays nearly identical to today — it just swaps self._session.get_items(...) for _gen.list_messages(self._session, ...).


Testing approach

The plan includes a strict no-regression testing strategy:

  1. Before touching any code: snapshot every public method signature and run the full integration test suite, saving both as baseline documents
  2. New unit tests (no credentials needed): mock-based tests that verify the generated layer builds the right HTTP calls and that the custom layer delegates correctly, preserves the generator container behavior, handles file uploads, converts AdaptiveCards, etc.
  3. Phase-by-phase: refactor one API file at a time, run unit tests after each, integration tests at the end
  4. Final gate: public API surface diff must be empty — zero method signatures changed

Full design spec

A detailed implementation spec lives on the working branch:
docs/openapi-integration-spec.md

It covers the full phase-by-phase plan, the generator design, the refactoring order for all 22 API files, the complete test matrix, the CI workflow design, a traceability table mapping every existing behavior to its test, and a rollback plan.


Questions for the community

We want to hear from you before starting implementation:

  1. Does this approach make sense to you? The generated layer is a new internal concept — does it feel like the right separation of concerns, or does it add unwanted complexity?

  2. Are there any existing behaviors we haven't accounted for? If you use the SDK in ways that rely on something subtle in the current implementation, please share — we want to catch it before the refactor, not after.

  3. Which new API families should we prioritize? Cloud Calling, Contact Center, BroadWorks, Device, UCM, Wholesale — all currently have zero SDK coverage. What would be most useful to expose first?

  4. Meetings generator: The meetings family already has a custom YAML-based generator. Should we migrate it to the OpenAPI generator, or keep both pipelines running in parallel?

  5. Generated files in the repo: Should _generated/*.py be committed to the repo (easy to read the current state, reviewable diffs in PRs) or always built from specs at install/build time (smaller repo, no stale generated code)? We lean toward committing them for transparency, but want input.

  6. Any concerns about the daily sync PR cadence? If Webex is updating specs nearly every day, that's potentially a lot of automated PRs. Should we batch them (e.g., weekly) or only open a PR when the generated Python output actually changes (not just the raw JSON)?


How to contribute

  • Comment below with feedback, concerns, or use cases we should account for
  • Review the full spec and flag anything that looks wrong
  • If you'd like to help implement any phase, say so — we'll coordinate

Thanks for reading this far. Looking forward to the discussion.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions