# Chapter 13 — GitLab Integration

> *`codecraftai-restclient-gitlab` is the framework's worked example of a
> real-world REST integration.  An OpenAPI spec auto-generates
> hundreds of method classes; a small hand-written foundation
> (RestClientBase, RestApiGroup, AspectsRest) carries the
> shared behaviour.  The chapter walks the generator pattern
> and the typical method shape.*

The portfolio includes `codecraftai-restclient-gitlab` as a worked example
of integrating with a REST API at scale.  GitLab's REST API
is large — hundreds of endpoints — and integrating with it
by hand would be infeasible.  Instead, the package
*generates* the client code from an OpenAPI specification;
the hand-written part is small and shared across all
generated methods.

This chapter walks the generator pattern, the auto-generated
output, the hand-written foundation, and the Aspects
integration for retry behaviour.  The patterns generalise to
any large REST API — projects building integrations with
their own external services follow the same shape.

## The package layout

```
codecraftai-restclient-gitlab/source/packages/ccai/gitlab/
└── rest/
    ├── restclientbase.py      ← hand-written foundation
    ├── restapigroup.py        ← hand-written
    ├── aspectsrest.py         ← hand-written
    ├── itemsresult.py         ← hand-written
    ├── clients/
    │   ├── gitlabrestclient.py    ← AUTO-GENERATED main client
    │   ├── groups/                ← AUTO-GENERATED operation groups
    │   ├── parameters/            ← AUTO-GENERATED parameter classes
    │   └── responses/             ← AUTO-GENERATED response classes
    ├── generation/
    │   └── restclientgenerator.py ← the generator itself
    └── descriptions/              ← OpenAPI YAML / JSON specs
```

Two halves:

- **Hand-written foundation**: `RestClientBase`,
  `RestApiGroup`, `AspectsRest`, `ItemsResult`.  Small,
  stable.
- **Auto-generated client**: hundreds of files under
  `clients/`.  Regenerated when the OpenAPI spec changes;
  not edited by hand.

The boundary is clear and enforced by a banner at the top of
every generated file.

## The auto-generated banner

**File:**
`codecraftai-restclient-gitlab/source/packages/ccai/gitlab/rest/generation/restclientgenerator.py:26`

```python
AUTO_GENERATED_BANNER = "# Auto-generated by RestClientGenerator. DO NOT EDIT.\n"
```

Every generated file starts with this banner.  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.

The banner is a *contract*: edits to generated files don't
survive regeneration.  Bug fixes in generated code must be
fixes to the generator.

## A representative auto-generated method

**File:**
`codecraftai-restclient-gitlab/source/packages/ccai/gitlab/rest/clients/groups/branches.py:16`

```python
def deleteapiv4projectsidrepositorybranchesbranch(
    self,
    params: Optional[parameters.BranchesDeleteapiv4projectsidrepositorybranchesbranchParams] = None,
    *,
    aspects: Optional[AspectsRest] = DEFAULT_REST_ASPECTS
) -> responses.BranchesDeleteapiv4projectsidrepositorybranchesbranchResponse:
    """
        Delete a branch
    """
    if params is None:
        params = parameters.BranchesDeleteapiv4projectsidrepositorybranchesbranchParams()
    call_leaf = "/projects/{id}/repository/branches/{branch}"
    path_params = params.path_params()
    if path_params:
        call_leaf = self.client.format_path(call_leaf, path_params)
    query_params = params.query_params()
    header_params = params.header_params()
    body_params = params.body_params()
    exp_status = [204]
    resp = self.client.perform_call(
        leaf=call_leaf,
        method="DELETE",
        params=query_params if query_params else None,
        body=body_params,
        exp_status=exp_status,
        headers=header_params if header_params else None,
        context_msg=None,
        aspects=aspects,
    )
    result = responses.BranchesDeleteapiv4projectsidrepositorybranchesbranchResponse(resp)
    return result
```

Five things to notice:

1. **The method name is the URL path.**
   `deleteapiv4projectsidrepositorybranchesbranch` is the
   verb plus path with `/` and `{}` and `:` stripped.
   Ugly but unambiguous — every endpoint has a unique method.
2. **Parameters are typed.**  The `params` argument has a
   class generated for this specific endpoint
   (`BranchesDeleteapiv4...Params`).  Parameter classes
   carry path / query / header / body parameters.
3. **The Aspects parameter.**  Per Volume 5 chapter 1's
   Aspects pattern, every call accepts a behaviour
   parameter.  Default is
   `DEFAULT_REST_ASPECTS`.
4. **Status validation.**  `exp_status = [204]` says "expect
   204 No Content."  The base client's `perform_call`
   surfaces a non-204 response as an error.
5. **Response wrapping.**  The raw HTTP response is wrapped
   in a typed response class
   (`BranchesDeleteapiv4...Response`).  Test code uses the
   wrapper's typed accessors rather than parsing JSON.

Every generated method has this shape.  The repetition is
why hand-writing is infeasible — but the repetition is also
why the generator works.

## The hand-written foundation

### `RestClientBase`

**File:**
`codecraftai-restclient-gitlab/source/packages/ccai/gitlab/rest/restclientbase.py:54`

```python
def __init__(self, host: str, credential: BaseCredential, port: Optional[int] = None,
             scheme: Optional[str] = "https", aspects: AspectsRest = DEFAULT_REST_ASPECTS):
    """
        Initialize the REST client base.
    """
    self._host = host
    self._credential = credential
    self._aspects = aspects

    self._host_url = f"{scheme}://{self._host}"
    if port is not None:
        self._host_url = f"{self._host_url}:{port}"

    self._session: Session = None
    return
```

The base class:

- Holds a host, credential, scheme, optional port.
- Holds default Aspects.
- Lazily creates a `requests.Session` for the connection
  pool.
- Provides `perform_call(...)` (above) that every generated
  method uses.

The credential authentication, the connection pooling, the
retry / polling behaviour through Aspects — all happen here.
Generated methods just construct parameters and call
`perform_call`.

### `RestApiGroup`

The base for the per-group classes (`Branches`, `Issues`,
`Projects`, etc.).  Each group is a small façade over the
`RestClientBase` instance, providing the methods for that
endpoint group.

### `AspectsRest`

The Aspects subclass for REST-shaped calls.  Carries:

- `completion_timeout` — total time before the call gives
  up.
- `connection_timeout` — time for individual TCP
  connections.
- `retry_count` — how many times to retry a failed call.
- `retry_pattern` — when to retry (`5xx_only`, `any_failure`,
  `none`).
- `polling_pattern` — when to poll (e.g., for async API
  responses).

Different calls need different aspects: a long-running CI
pipeline trigger wants a high `completion_timeout`; a quick
metadata query wants a low `connection_timeout` and an
aggressive retry on connection-failure.  The Aspects
parameter on every method lets the caller tune per-call.

## Authentication

The `BaseCredential` integration:

```python
def authenticate(self):
    """
        Apply credential to the session.  Override per-credential-type.
    """
    if self._session is None:
        self._session = Session()

    if isinstance(self._credential, BasicAuthCredential):
        self._session.auth = (self._credential.username, self._credential.password)
    elif isinstance(self._credential, BearerTokenCredential):
        self._session.headers["Authorization"] = f"Bearer {self._credential.token}"
    # ... etc
```

Authentication happens at session-creation time.  The
credential is set on the requests session; subsequent calls
inherit it.  The session-level auth means generated methods
don't need to know about credentials — they call
`perform_call`, which uses the already-authenticated session.

## A worked test using the GitLab integration

Suppose a CI orchestration's worker test verifies a branch
exists in a project:

```python
import testbase
from ccai.gitlab.rest.clients.gitlabrestclient import GitLabRestClient
from ccai.credentials.bearertokencredential import BearerTokenCredential

@testbase.attach.parameter(landscape, identifier="landscape")
def test_branch_exists_in_project(landscape):
    """
        Verify the configured branch exists in the project.
    """
    creds = landscape.credentials_manager.lookup_by_name("gitlab-readonly")
    bearer = BearerTokenCredential(token=creds.token)

    client = GitLabRestClient(host="gitlab.internal", credential=bearer)

    from ccai.gitlab.rest.clients.parameters import (
        BranchesGetapiv4projectsidrepositorybranchesParams as ListBranchesParams
    )

    params = ListBranchesParams(
        path={"id": "my-project"},
        query={"search": "main"},
    )
    result = client.branches.getapiv4projectsidrepositorybranches(params=params)

    rtnval = any(b.name == "main" for b in result.items)
    assert rtnval
    return
```

The test:

- Resolves the GitLab credential through the framework's
  credential manager (Volume 2 chapter 11).
- Constructs a `GitLabRestClient` with the credential.
- Calls a generated method with a typed parameter.
- Iterates the typed response.

The GitLab REST API has hundreds of endpoints; the project's
test exercises one or a few.  The same pattern applies to
*any* of GitLab's endpoints — the test author picks the
right method and the right parameter class.

## When the OpenAPI spec changes

The integration's evolution loop:

1. GitLab releases a new API version with new endpoints.
2. The new OpenAPI spec is downloaded.
3. The generator runs against the new spec.
4. New generated files appear; existing files may change
   (response shapes, parameter additions).
5. The hand-written foundation usually doesn't need changes
   — the methods that change are all auto-generated.

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

## What this pattern generalises to

The auto-generation 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.

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-gitlab` package is one worked example; a project
integrating with its own internal REST API can use the same
shape.

## Smoke vs comprehensive testing strategy

A package with hundreds of auto-generated methods raises the
question: *which methods do you test?*

The portfolio's discipline:

- **Smoke-test the foundation.**  `RestClientBase`'s
  authentication, connection pooling, retry behaviour, and
  Aspects handling are tested explicitly.  Failures in
  these affect *every* generated method.
- **Smoke-test the generator.**  Run the generator against a
  reference OpenAPI spec; verify the output is well-formed
  Python.
- **Smoke-test a representative method per group.**  One
  method from each major group (Branches, Issues, Projects,
  etc.) verifies the end-to-end happy path.
- **Don't comprehensively test every method.**  100% coverage
  of generated methods would mean writing N similar tests for
  N similar methods — high cost, low marginal value.

The trade-off makes peace with the realistic constraint that
no team has the budget to test 500+ methods individually.
Smoke-test the substrate; trust the generator for the rest.

## What the next chapter does

Chapter 14 walks the **Flask web-service scaffolding** —
`codecraftai-webservice-flask`.  Where GitLab integration is
the *consumer* side of REST (we call other people's APIs),
the Flask scaffolding is the *server* side (we build APIs
others can call).  The chapter walks `SecurityBlueprint`,
`RouteSecurityManager`, OAuth provider integration, and the
SQLAlchemy ORM patterns.
