# Chapter 7 — The Auto-Generated GitHub Public-API Client

> *GitHub's public REST API has hundreds of endpoints.
> Hand-writing a client for it is infeasible.
> `codecraftai-restclient-github-public` is the worked example of
> auto-generation: 571 generated modules built on the
> foundation from chapter 6.  This chapter walks what's worth
> testing in such a package and what to trust by construction.*

`codecraftai-restclient-github-public` is the largest package in
the volume by file count — roughly 571 Python modules —
because GitHub's API is enormous.  The package is a practical
demonstration that the foundation classes from chapter 6
scale.  The chapter walks how a generated client is
structured, what tests exercise it, and the
*smoke-vs-comprehensive* testing strategy the framework
recommends for any auto-generated client at this scale.

## What's in the package

```
codecraftai-restclient-github-public/source/packages/ccai/
├── github/
│   └── rest/
│       └── clients/
│           └── public/
│               ├── githubpublicrestclient.py   # the entry-point client
│               ├── groups/                     # 42 group files
│               ├── schemas/                    # ~525 schema files
│               └── ...
└── descriptions/                                # 76 OpenAPI JSON spec files
```

Counting:

- **571 Python modules** — generated from the OpenAPI spec.
- **525 schema classes** — one per JSON schema in the spec.
- **42 operation groups** — one per logical API tag (repos,
  issues, pulls, actions, etc.).
- **76 spec JSON files** — the OpenAPI source split into
  manageable chunks.

The package is mechanically generated.  Edits to generated
files don't survive regeneration.

## The auto-generated banner

Every generated file starts with:

```python
# Auto-generated by RestClientGenerator. DO NOT EDIT.
```

This banner is the *contract*.  An engineer attempting to fix
a bug in a generated method by editing the generated file
would see the banner and know to fix the *generator* (or the
OpenAPI spec) instead.  Edits to generated files do not
survive regeneration.

The banner is also a useful greppable marker.  A reader
exploring the codebase can quickly distinguish
hand-written code (no banner) from generated code (banner).

## A representative generated method

**File:**
`source/packages/ccai/github/rest/clients/public/groups/repos.py:84–162`

```python
def accept_invitation_for_authenticated_user(
    self,
    invitation_id: int,
    *,
    exp_status: Optional[List[int]] = None,
    headers: Optional[Dict[str, str]] = None,
    aspects: Optional[AspectsRest] = None,
) -> Any:
    """
        Accept a repository invitation
    """
    client = self.client
    if aspects is None:
        aspects = client.aspects

    header_params = client.headers
    if headers is not None:
        header_params = headers

    leaf_template = "/user/repository_invitations/{invitation_id}"
    path_params = {
        'invitation_id': invitation_id,
    }
    call_leaf = leaf_template.format(**path_params)

    query_params = None

    if exp_status is None:
        exp_status = [204]

    resp = client.perform_call(
        leaf=call_leaf,
        method="PATCH",
        params=query_params if query_params else None,
        body=None,
        exp_status=exp_status,
        headers=header_params if header_params else None,
        context_msg=None,
        aspects=aspects,
    )
    return resp.json()
```

A more complex generated method that takes a body and parses
the response into typed schema objects:

```python
def add_app_access_restrictions(
    self,
    owner: str, repo: str, branch: str,
    body: Dict[str, Any],
    *,
    exp_status: Optional[List[int]] = None,
    headers: Optional[Dict[str, str]] = None,
    aspects: Optional[AspectsRest] = None,
) -> List[Integration]:
    """
        Add app access restrictions

        Protected branches are available in public repositories
        with GitHub Free and GitHub Free for organizations, ...
    """
    client = self.client
    if aspects is None:
        aspects = client.aspects

    header_params = client.headers
    if headers is not None:
        header_params = headers

    leaf_template = "/repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"
    path_params = {
        'owner': owner,
        'repo': repo,
        'branch': branch,
    }
    call_leaf = leaf_template.format(**path_params)

    query_params = None

    if exp_status is None:
        exp_status = [200]

    resp = client.perform_call(
        leaf=call_leaf,
        method="POST",
        params=query_params if query_params else None,
        body=body,
        exp_status=exp_status,
        headers=header_params if header_params else None,
        context_msg=None,
        aspects=aspects,
    )
    raw_data = resp.json()
    items = [self._parse_item(item, Integration) for item in raw_data]
    return items
```

Five steps repeat across hundreds of methods:

1. Resolve aspects (use caller's, fall back to client's
   default).
2. Format the URL path with caller-supplied parameters.
3. Determine expected statuses (allow caller override).
4. Call `client.perform_call` (chapter 6's foundation).
5. Parse the response — `resp.json()` for opaque returns,
   `_parse_item(...)` for typed-schema returns.

The repetition is *the point* of generation.  Every method
follows the same template; the generator instantiates the
template per OpenAPI operation.  Reviewers who can read one
method can read all of them.

## The entry-point client

**File:**
`source/packages/ccai/github/rest/clients/public/githubpublicrestclient.py:53–70`

```python
class GithubPublicRestClient(GithubRestClientBase):
    """
        GitHub public REST API client generated from the
        OpenAPI specification.
    """

    def __init__(self, host: str, credential: BaseCredential,
                 port: Optional[int] = None,
                 scheme: Optional[str] = "https",
                 aspects: AspectsRest = DEFAULT_REST_ASPECTS):
        super().__init__(host, credential, port, scheme=scheme, aspects=aspects)

        self._grp_actions = ActionsApiGroup(self)
        self._grp_activity = ActivityApiGroup(self)
        self._grp_apps = AppsApiGroup(self)
        self._grp_billing = BillingApiGroup(self)
        # ... 38 more groups instantiated as attributes ...
        self._grp_repos = ReposApiGroup(self)
        # ...
```

The client's constructor:

1. Calls the GitHub-specific base class's constructor
   (`GithubRestClientBase` adds the `Accept:
   application/vnd.github.v3+json` header).
2. Instantiates each of the 42 API groups as a private
   attribute.
3. Exposes each as a public property:

```python
    @property
    def actions(self) -> "ActionsApiGroup":
        return self._grp_actions

    @property
    def repos(self) -> "ReposApiGroup":
        return self._grp_repos

    # ... etc.
```

Test code uses the groups directly:

```python
client = GithubPublicRestClient("api.github.com", bearer_credential)
result = client.repos.list_user_repos(username="alice")
```

The flow: client → group → method → `perform_call` →
HTTP → response → parsed schema → returned to test.  Every
generated method follows the same path.

## The hand-written customisation point

**File:** `githubrestclientbase.py:34–49`

```python
class GithubRestClientBase(RestClientBase):
    """
        GitHub-specific REST client base.

        Adds the GitHub Accept header for API versioning.
    """

    def __init__(self, host: str, credential: BaseCredential,
                 port: Optional[int] = None,
                 scheme: Optional[str] = "https",
                 aspects: AspectsRest = DEFAULT_REST_ASPECTS):
        # GitHub-specific: pin to API v3 via Accept header.
        headers = {
            "Accept": "application/vnd.github.v3+json",
        }
        super().__init__(
            host=host, credential=credential, port=port, scheme=scheme,
            headers=headers, aspects=aspects,
        )
        return
```

This is the *one hand-written class* that customises the
auto-generated client.  Two things it does:

- Adds the `Accept` header to pin to GitHub API v3.
- Inherits everything else from `RestClientBase`.

The customisation point is small because GitHub's REST API
is well-behaved — most of its specifics are already in the
OpenAPI spec the generator consumed.  A different API might
need more customisation (custom auth flow, request signing,
etc.); the pattern is the same — subclass `RestClientBase`,
add what's specific.

## What gets tested

A fair question for a 571-module package: *which methods do
you test?*

The portfolio's discipline is **smoke-vs-comprehensive**:

### Smoke-test the foundation classes

`RestClientBase`, `AspectsRest`, `RestApiGroup`,
`ItemsResult`, `RestClientGenerator` get explicit unit tests.
Failures in these affect *every* generated method, so testing
them is high-leverage.

### Smoke-test the generator itself

The generator is run against a reference OpenAPI spec; the
output is verified to be well-formed Python (parses,
imports, types check).  This catches generator bugs before
they propagate to hundreds of generated methods.

### Smoke-test a representative method per group

One method from each of the 42 GitHub groups (repos /
issues / pulls / actions / etc.) is exercised as an
integration test.  This catches:

- Authentication issues affecting that group.
- Path templating issues.
- Response parsing issues for that group's schemas.

The chosen methods are typically *idempotent* (GET requests,
not POSTs that mutate state) and *well-known* (e.g., "get
the authenticated user" rather than an obscure endpoint).

### Don't comprehensively test every method

Writing 571 individual method tests would be:

- **Expensive** to write — at least a day per method to
  cover happy path, error cases, parameter variations.
- **Expensive to maintain** — every spec update could
  require updates to every test.
- **Low marginal value** — methods fail in *patterns*, not
  individually.  A change to `perform_call` that breaks one
  method probably breaks them all; testing one catches it.

The trade-off makes peace with the realistic budget
constraint.  Smoke-test the substrate; trust the generator
for the rest; rely on integration tests at the next layer
(tests that *use* the GitHub client to exercise specific
workflows) to catch real bugs.

## When the API spec changes

GitHub releases new API versions.  The integration's
evolution loop:

1. **Update the OpenAPI spec.**  Drop the new spec into
   `descriptions/`.
2. **Run the generator.**  `RestClientGenerator` regenerates
   the schemas, groups, and client.
3. **Review generator diffs.**  New methods appear; existing
   methods may have parameter additions; response shapes
   may change.
4. **Run the smoke tests.**  Most should still pass; failures
   indicate spec changes that need consumer-side updates.
5. **Hand-written code usually doesn't change.**
   `GithubRestClientBase` (the API-version pin) only
   changes when GitHub deprecates v3.

A small caveat: when the generator itself needs to evolve
(e.g., to support a new OpenAPI feature like callbacks or
webhooks), the *generator's* code is hand-written and gets
edited.  But the generated client code stays in sync
mechanically.

## What this pattern generalises to

Volume 6 chapter 13 walked GitLab integration using the same
pattern.  The pattern works whenever an external API
provides a machine-readable specification:

- **OpenAPI / Swagger** for REST APIs.
- **Protocol Buffers** for gRPC APIs.
- **WSDL** for SOAP services.
- **AsyncAPI** for event-driven APIs.
- **GraphQL schema** for GraphQL APIs (with appropriate
  generator extensions).

For each, the framework's pattern is the same: hand-write a
small shared foundation, write a generator that consumes the
spec, regenerate when the spec changes.  The
`codecraftai-restclient-github-public` package is one worked
example of an OpenAPI-driven generator at scale.

## Reading order for a contributor

A reader wanting to *contribute* to the GitHub client (or
fork it for a similar API) should read in this order:

1. **`RestClientBase`** (chapter 6) — the foundation.
2. **`RestClientGenerator`** in `codecraftai-restclient-base`
   — how generation works.
3. **`GithubRestClientBase`** — the hand-written
   customisation.
4. **One representative group** (e.g.,
   `groups/repos.py`) — to see what the generator
   produces.
5. **One representative schema** (e.g.,
   `schemas/Repository`) — to see how typed parsing
   works.

By the end, the reader can run the generator against an
updated spec and ship the new client.

## What this chapter set up

A short chapter for a large package.  The reader should now:

- Understand auto-generation as a viable pattern for large
  REST APIs.
- Recognise the auto-generated banner as the signal that a
  file isn't hand-edited.
- Know the smoke-vs-comprehensive testing strategy.
- See the connection between
  `codecraftai-restclient-github-public` (this chapter) and
  `codecraftai-restclient-gitlab` (Volume 6 chapter 13) — same pattern,
  different APIs.

The next chapter walks the four protocol extension packages
— UPnP, Tasker, Bluetooth, CAN.  Where this chapter walked
*one* large auto-generated package, chapter 8 walks *four*
smaller hand-written packages, each adding a protocol the
core `codecraftai-interop` doesn't ship.
