Skip to content

Commit 3a97dc5

Browse files
authored
feat: add Partner API client for distributor operations (#74)
This PR introduces a new Partner API client to `python-exoscale`, enabling distributors to programmatically manage their sub-organizations under the new `OpenAPI` spec of `Partner API`. The implementation follows the patterns from the V2 API client. The Partner API client is dynamically generated from the `OpenAPI` specification, similar to how the V2 client works. The GitHub Actions workflow has been updated to also fetch Partner API specifications hourly, maintaining them as static files within the package.
1 parent 090a352 commit 3a97dc5

8 files changed

Lines changed: 905 additions & 0 deletions

File tree

.github/workflows/openapi.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
shell: bash
2020
run: |-
2121
curl https://openapi-v2.exoscale.com/source.json | ./.sort-enums.py | jq > exoscale/openapi.json
22+
23+
curl https://partner-api.exoscale.com/v1.alpha/openapi.json | ./.sort-enums.py | jq > exoscale/partner-api.json
2224
- name: Commit and push if changed
2325
run: |-
2426
git config user.name "Automated"

docs/changes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
---------
33

4+
0.15.0 (unreleased)
5+
-------------------
6+
7+
* Add Partner API client for distributor operations
8+
* Partner API client follows same patterns as V2 API
9+
* Automatic hourly updates of Partner API OpenAPI spec
10+
411
0.14.0 (2025-08-28)
512
-------------------
613

docs/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ This provides Python bindings for the `Exoscale`_ cloud platform API.
1010
versions: Python 3.9+. Older Python versions may be compatible but such
1111
compatibility is not guaranteed.
1212

13+
Installation
14+
------------
15+
16+
Install the package using pip::
17+
18+
pip install exoscale
19+
1320
.. toctree::
1421
:maxdepth: 2
1522

1623
v2
24+
partner
1725
changes

docs/partner.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
Partner API
2+
===========
3+
4+
The Partner API client provides access to distributor operations for managing
5+
sub-organizations in Exoscale.
6+
7+
Basic Usage
8+
-----------
9+
10+
Creating a client
11+
~~~~~~~~~~~~~~~~~
12+
13+
.. code-block:: python
14+
15+
from exoscale.api.partner import Client
16+
17+
# Create client with API credentials
18+
client = Client(
19+
key="EXO...",
20+
secret="..."
21+
)
22+
23+
Managing Organizations
24+
~~~~~~~~~~~~~~~~~~~~~~
25+
26+
.. code-block:: python
27+
28+
# List all distributor organizations
29+
result = client.list_distributor_organizations()
30+
for org in result['organizations']:
31+
print(f"{org['name']} - {org['id']}")
32+
33+
# Create a new organization
34+
new_org = client.create_distributor_organization(
35+
organization={
36+
"name": "Customer Corp",
37+
"address": "123 Business Ave",
38+
"city": "Zurich",
39+
"postcode": "8001",
40+
"country": "CH",
41+
"owner-email": "admin@customer.com",
42+
"client-id": "internal-123" # Optional
43+
}
44+
)
45+
46+
# Activate/Suspend organizations
47+
client.activate_distributor_organization(id=org_id)
48+
client.suspend_distributor_organization(id=org_id)
49+
50+
# Get usage information
51+
usage = client.list_distributor_organization_usage(
52+
id=org_id,
53+
period="2025-01"
54+
)
55+
56+
Error Handling
57+
--------------
58+
59+
The Partner API client uses the same error handling as the V2 API:
60+
61+
.. code-block:: python
62+
63+
from exoscale.api.exceptions import (
64+
ExoscaleAPIAuthException,
65+
ExoscaleAPIClientException,
66+
ExoscaleAPIServerException
67+
)
68+
69+
try:
70+
client.get_distributor_organization(id="invalid")
71+
except ExoscaleAPIClientException as e:
72+
print(f"Client error: {e}")
73+
except ExoscaleAPIServerException as e:
74+
print(f"Server error: {e}")
75+
76+
API Reference
77+
-------------
78+
79+
.. autoclass:: exoscale.api.partner.Client
80+
:members:
81+
:inherited-members:

exoscale/api/generator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def _return_docstring(api_spec, operation):
106106

107107
return "\n ".join(status_codes_docs)
108108

109+
109110
class BaseClient:
110111
_api_spec = None
111112
_by_operation = None

exoscale/api/partner.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Exoscale Partner API client
3+
4+
This module provides a client for the Exoscale Partner API, which allows
5+
distributors to manage sub-organizations.
6+
"""
7+
8+
import json
9+
from pathlib import Path
10+
11+
from .generator import create_client_class
12+
from .v2 import Client as V2Client
13+
14+
15+
with open(Path(__file__).parent.parent / "partner-api.json", "r") as f:
16+
partner_api_spec = json.load(f)
17+
BasePartnerClient = create_client_class(partner_api_spec)
18+
19+
20+
class Client(BasePartnerClient):
21+
"""
22+
Partner API client with Exoscale authentication.
23+
24+
This client provides access to distributor operations for managing
25+
sub-organizations. It uses the same authentication mechanism as the
26+
V2 API client.
27+
28+
Args:
29+
key (str): Exoscale API key
30+
secret (str): Exoscale API secret
31+
url (str): Override endpoint URL (optional)
32+
zone (str): Exoscale zone (optional)
33+
34+
Example:
35+
>>> from exoscale.api.partner import Client
36+
>>> client = Client("EXO...", "secret")
37+
>>> orgs = client.list_distributor_organizations()
38+
"""
39+
40+
def __init__(self, key, secret, *args, url=None, **kwargs):
41+
# Initialize with Partner API endpoint
42+
partner_url = (
43+
url if url else "https://partner-api.exoscale.com/v1.alpha"
44+
)
45+
super().__init__(*args, url=partner_url, **kwargs)
46+
47+
# Reuse the v2 client's authentication mechanism
48+
v2_client = V2Client(key, secret, *args, url=url, **kwargs)
49+
50+
self.http_client = v2_client.http_client
51+
self.key = key
52+
53+
self._v2_client = v2_client
54+
55+
def __repr__(self):
56+
return (
57+
f"<Client endpoint={self.endpoint} "
58+
f"key={self.key} secret=**masked**>"
59+
)

0 commit comments

Comments
 (0)