Generated Output¶
This page explains the structure and contents of the client packages produced by pclient-build.
Multi-variant output¶
A single invocation generates three client variants, each in its own subdirectory:
generated/
├── python_requests/ Python client using requests
│ └── payments_client/
├── python_httpx/ Python client using httpx
│ └── payments_client/
└── go/ Go client using net/http
└── payments-client/
Python output¶
Both Python variants (python_requests/ and python_httpx/) produce the same package structure. The only difference is the HTTP transport library used.
Flat layout (no versioning)¶
When your API paths don't contain version prefixes, the generator produces a flat package:
payments_client/
├── __init__.py # Package init with imports
├── client.py # HTTP client class
├── ext.py # Pyramid includeme extension
└── schemas.py # Marshmallow schemas (only if schemas exist)
Versioned layout¶
When your endpoints have version prefixes (e.g., /api/v1/charges, /api/v2/charges), the generator creates per-version subdirectories:
payments_client/
├── __init__.py
├── client.py # Root client with version sub-client properties
├── ext.py
├── schemas.py # Schemas for unversioned endpoints (if any)
├── v1/
│ ├── __init__.py
│ ├── client.py # V1Client with v1 endpoints
│ └── schemas.py # Schemas for v1 endpoints
└── v2/
├── __init__.py
├── client.py # V2Client with v2 endpoints
└── schemas.py # Schemas for v2 endpoints
Python file-by-file breakdown¶
client.py¶
The main client class. Each endpoint becomes a method:
class PaymentsClient:
"""Auto-generated HTTP client for the payments Pyramid application."""
def __init__(self, base_url, auth_token=None, timeout=30):
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self.session = requests.Session()
if auth_token:
self.session.headers["Authorization"] = f"Bearer {auth_token}"
# Version sub-clients (only in versioned output)
self.v1 = V1Client(self.base_url, self.session, self.timeout)
def list_charges(self):
"""GET /api/v1/charges"""
url = f"{self.base_url}/api/v1/charges"
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
return ChargesResponseSchema().load(response.json())
def create_charge(self, amount, currency):
"""POST /api/v1/charges"""
url = f"{self.base_url}/api/v1/charges"
body = ChargesRequestSchema().dump(
{"amount": amount, "currency": currency}
)
response = self.session.post(url, json=body, timeout=self.timeout)
response.raise_for_status()
return ChargeResponseSchema().load(response.json())
Key behaviors:
- Path parameters become required positional arguments
- Body parameters become keyword arguments, serialized through the request schema's
dump() - Querystring parameters become optional keyword arguments
- Responses are deserialized through the response schema's
load(), or returned as rawresponse.json()when no schema exists - Errors raise via
response.raise_for_status()(standardrequestsbehavior)
schemas.py¶
Copies of your server's Marshmallow schemas, renamed by role:
from marshmallow import Schema, fields
class ChargesRequestSchema(Schema):
amount = fields.Integer(required=True)
currency = fields.String(required=True)
class ChargeResponseSchema(Schema):
id = fields.Integer()
amount = fields.Integer()
currency = fields.String()
status = fields.String()
This file is only generated when your endpoints have Marshmallow schemas attached. See Features > Schema renaming for how names are derived.
ext.py¶
A Pyramid extension that registers the client on the request:
def includeme(config):
def payments_client(request):
return PaymentsClient(
base_url=request.registry.settings["payments.base_url"],
auth_token=request.registry.settings.get("payments.auth_token"),
)
config.add_request_method(
payments_client, name="payments_client", reify=True
)
The client is created once per request (reify=True) and reads its configuration from the app's INI settings.
__init__.py¶
Package init that imports the client class and schemas for convenience:
from payments_client.client import PaymentsClient
from payments_client.schemas import ChargesRequestSchema, ChargeResponseSchema
Version sub-client (v1/client.py)¶
In versioned output, each version gets its own client class that shares the parent's session:
class V1Client:
"""Auto-generated HTTP client for payments API v1."""
def __init__(self, base_url, session, timeout):
self.base_url = base_url
self.session = session
self.timeout = timeout
def list_charges(self):
"""GET /api/v1/charges"""
# ...
Authentication is configured once on the root client and shared across all versions.
Go output¶
The Go variant produces an idiomatic Go module with structs, functional options, and standard library HTTP.
Flat layout (no versioning)¶
payments-client/
├── go.mod # Go module definition
├── README.md # Usage documentation
├── client.go # Client struct, NewClient, methods
└── types.go # Go structs from Marshmallow schemas (if any)
Versioned layout¶
payments-client/
├── go.mod
├── README.md
├── client.go # Root client with version sub-client fields
├── types.go # Structs for unversioned schemas (if any)
├── v1/
│ ├── client.go # V1Client with v1 methods
│ └── types.go # Structs for v1 schemas
└── v2/
├── client.go # V2Client with v2 methods
└── types.go # Structs for v2 schemas
Go file-by-file breakdown¶
go.mod¶
Declares the Go module with no external dependencies:
client.go¶
The main client struct with functional options:
package paymentsclient
type Client struct {
BaseURL string
AuthToken string
Timeout int
HTTPClient *http.Client
V1 *v1.V1Client
}
func NewClient(baseURL string, opts ...Option) *Client {
c := &Client{
BaseURL: strings.TrimRight(baseURL, "/"),
Timeout: 30,
HTTPClient: &http.Client{},
}
for _, opt := range opts {
opt(c)
}
c.HTTPClient.Timeout = time.Duration(c.Timeout) * time.Second
c.V1 = v1.NewV1Client(c.BaseURL, c.AuthToken, c.HTTPClient)
return c
}
type Option func(*Client)
func WithAuthToken(token string) Option {
return func(c *Client) { c.AuthToken = token }
}
func WithTimeout(seconds int) Option {
return func(c *Client) { c.Timeout = seconds }
}
Key behaviors:
- Path parameters become individual
stringarguments - Request body is accepted as a pointer to a request struct (e.g.,
*CreateChargeRequest) - Responses return
(*ChargeResponseSchema, error)when a schema exists, or(map[string]interface{}, error)otherwise - Errors are returned as Go's
(T, error)pattern — non-2xx status codes produce an error
types.go¶
Go structs generated from Marshmallow schemas:
package paymentsclient
type ChargesRequestSchema struct {
Amount int `json:"amount"`
Currency string `json:"currency"`
}
type ChargeResponseSchema struct {
ID *int `json:"id,omitempty"`
Amount *int `json:"amount,omitempty"`
Currency *string `json:"currency,omitempty"`
Status *string `json:"status,omitempty"`
}
Type mapping from Marshmallow to Go:
| Marshmallow field | Go type (required) | Go type (optional) |
|---|---|---|
fields.String |
string |
*string |
fields.Integer |
int |
*int |
fields.Float |
float64 |
*float64 |
fields.Boolean |
bool |
*bool |
fields.DateTime |
time.Time |
*time.Time |
fields.List |
[]interface{} |
[]interface{} |
fields.Dict |
map[string]interface{} |
map[string]interface{} |
| Other | interface{} |
interface{} |
Optional fields use pointer types with omitempty JSON tags, following Go convention for distinguishing zero values from absent values.
Version sub-client (v1/client.go)¶
package v1
type V1Client struct {
baseURL string
authToken string
httpClient *http.Client
}
func NewV1Client(baseURL, authToken string, httpClient *http.Client) *V1Client {
return &V1Client{
baseURL: baseURL,
authToken: authToken,
httpClient: httpClient,
}
}
func (c *V1Client) ListCharges(req *ChargesQuerySchema) (*ChargesResponseSchema, error) {
// ...
}
Authentication is configured once on the root client and passed down to version sub-clients.
Conditional generation¶
schemas.py/types.goare only generated when endpoints have Marshmallow schemas. Plain Pyramid routes without Cornice/Marshmallow still produce a working client — Python methods return rawresponse.json(), Go methods returnmap[string]interface{}.- Version subdirectories are only created when versioned paths are detected. If all your paths are flat, you get the simple flat layout.