Skip to content

Commit 7eacbbf

Browse files
authored
feat(sdk): improve Python SDK API (#3552)
1 parent 1b2940a commit 7eacbbf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+23243
-22719
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ generate-javascript-sdk: ## Generate JavaScript SDK
3131
.PHONY: gen-api
3232
gen-api: update-openapi generate-javascript-sdk ## Generate API and SDKs
3333
$(call print-target)
34-
# dagger call generate python-sdk -o api/client/python
3534

3635
.PHONY: generate-all
3736
generate-all: update-openapi generate-javascript-sdk ## Execute all code generators

api/api.gen.go

Lines changed: 335 additions & 335 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/client/go/client.gen.go

Lines changed: 282 additions & 282 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/client/python/README.md

Lines changed: 186 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
# Examples
22

3+
## Setup
4+
35
Install dependencies
46

57
```sh
68
poetry install
79
```
810

9-
Run examples
11+
## Running Examples
12+
13+
Run any example with environment variables:
1014

1115
```sh
1216
OPENMETER_ENDPOINT=https://openmeter.cloud \
1317
OPENMETER_TOKEN=om_xxx \
1418
poetry run python ./sync/ingest.py
1519
```
20+
21+
## Type Checking
22+
23+
The examples are type-checked using **Pyright**:
24+
25+
```sh
26+
poetry run pyright
27+
```
28+
29+
**Note**: Mypy is not compatible with the generated SDK models due to how it handles overloaded constructors. Pyright is the recommended type checker for this project.

api/client/python/examples/async/customer.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
CustomerCreate,
88
CustomerReplaceUpdate,
99
CustomerUsageAttribution,
10+
Metadata,
1011
)
1112
from corehttp.exceptions import HttpResponseError
1213

@@ -25,41 +26,45 @@ async def main() -> None:
2526
# Create a customer
2627
customer_create = CustomerCreate(
2728
name="Acme Corporation",
28-
key=customer_key,
29+
usage_attribution=CustomerUsageAttribution(subject_keys=[subject_key]),
2930
description="A demo customer for testing",
31+
metadata=Metadata(
32+
{
33+
"industry": "technology",
34+
}
35+
),
36+
key=customer_key,
3037
primary_email="[email protected]",
3138
currency="EUR",
32-
usage_attribution=CustomerUsageAttribution(subject_keys=[subject_key]),
33-
metadata={
34-
"industry": "technology",
35-
},
3639
)
3740

38-
created_customer = await client.customer.customers.create(customer_create)
41+
created_customer = await client.customers.create(customer_create)
3942
print(f"Customer created successfully with ID: {created_customer.id}")
4043
print(f"Customer name: {created_customer.name}")
4144
print(f"Customer key: {created_customer.key}")
4245

4346
# Get the customer by ID or key
44-
customer = await client.customer.customers.get(created_customer.id)
47+
customer = await client.customers.get(created_customer.id)
4548
print(f"\nRetrieved customer: {customer.name}")
4649
print(f"Primary email: {customer.primary_email}")
4750
print(f"Currency: {customer.currency}")
4851

4952
# Update the customer
5053
customer_update = CustomerReplaceUpdate(
5154
name="Acme Corporation Ltd.",
52-
key=customer_key,
55+
usage_attribution=CustomerUsageAttribution(subject_keys=[subject_key]),
5356
description="Updated demo customer",
57+
metadata=Metadata(
58+
{
59+
"industry": "technology",
60+
}
61+
),
62+
key=customer_key,
5463
primary_email="[email protected]",
5564
currency="USD",
56-
usage_attribution=CustomerUsageAttribution(subject_keys=[subject_key]),
57-
metadata={
58-
"industry": "technology",
59-
},
6065
)
6166

62-
updated_customer = await client.customer.customers.update(created_customer.id, customer_update)
67+
updated_customer = await client.customers.update(created_customer.id, customer_update)
6368
print(f"\nCustomer updated successfully")
6469
print(f"Updated name: {updated_customer.name}")
6570
print(f"Updated email: {updated_customer.primary_email}")

api/client/python/examples/async/entitlement.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
ENDPOINT: str = environ.get("OPENMETER_ENDPOINT") or "https://openmeter.cloud"
99
token: Optional[str] = environ.get("OPENMETER_TOKEN")
1010
customer_key: str = environ.get("OPENMETER_CUSTOMER_KEY") or "acme-corp-1"
11-
feature_key: str = environ.get("OPENMETER_FEATURE_KEY") or "api-access"
11+
feature_key: str = environ.get("OPENMETER_FEATURE_KEY") or "api_access"
1212

1313

1414
async def main() -> None:
@@ -20,7 +20,7 @@ async def main() -> None:
2020
# Check customer access to a specific feature
2121
print(f"Checking access for customer '{customer_key}' to feature '{feature_key}'...")
2222

23-
entitlement_value = await client.entitlements.customer_entitlement.get_customer_entitlement_value(
23+
entitlement_value = await client.customer_entitlement.get_customer_entitlement_value(
2424
customer_key, feature_key
2525
)
2626

@@ -39,9 +39,37 @@ async def main() -> None:
3939
if entitlement_value.config is not None:
4040
print(f"Config: {entitlement_value.config}")
4141

42+
# List customer entitlements and demonstrate type-specific handling
43+
print(f"\nListing all entitlements for customer '{customer_key}'...")
44+
entitlements_response = await client.customer_entitlements_v2.list(customer_key)
45+
46+
print(f"\nEntitlements by Type:")
47+
for entitlement in entitlements_response.items_property:
48+
# Note: Due to a deserialization issue in the SDK, items come back as dicts
49+
# Access fields using dict syntax or .get()
50+
print(f"\n Feature: {entitlement.get('featureKey')}")
51+
print(f" ID: {entitlement.get('id')}")
52+
53+
# Handle different entitlement types using discriminator
54+
entitlement_type = entitlement.get("type")
55+
if entitlement_type == "metered":
56+
# Metered entitlement
57+
print(f" Type: Metered")
58+
print(f" Soft Limit: {entitlement.get('isSoftLimit')}")
59+
if entitlement.get("issueAfterReset") is not None:
60+
print(f" Issue After Reset: {entitlement.get('issueAfterReset')}")
61+
elif entitlement_type == "static":
62+
# Static entitlement
63+
print(f" Type: Static")
64+
if entitlement.get("config") is not None:
65+
print(f" Config: {entitlement.get('config')}")
66+
elif entitlement_type == "boolean":
67+
# Boolean entitlement
68+
print(f" Type: Boolean")
69+
4270
# Get overall customer access to all features
4371
print(f"\nGetting overall access for customer '{customer_key}'...")
44-
customer_access = await client.entitlements.customer.get_customer_access(customer_key)
72+
customer_access = await client.customer.get_customer_access(customer_key)
4573

4674
print(f"\nCustomer Access Summary:")
4775
print(f"Total entitlements: {len(customer_access.entitlements)}")

api/client/python/examples/async/query.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44

55
from openmeter.aio import Client
6-
from openmeter.models import MeterQueryResult
6+
from openmeter.models import MeterQueryResult, FilterString
77
from corehttp.exceptions import HttpResponseError
88

99
ENDPOINT: str = environ.get("OPENMETER_ENDPOINT") or "https://openmeter.cloud"
@@ -35,7 +35,7 @@ async def main() -> None:
3535
# Query total values for model=gpt-4o
3636
r = await client.meters.query_json(
3737
meter_id_or_slug="tokens_total",
38-
advanced_meter_group_by_filters={"model": {"$eq": "gpt-4o"}},
38+
advanced_meter_group_by_filters={"model": FilterString(eq="gpt-4o")},
3939
)
4040
if r.data and len(r.data) > 0:
4141
print("Query total values for model=gpt-4o:", r.data[0].value)

api/client/python/examples/async/subscription.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from openmeter.aio import Client
66
from openmeter.models import (
7+
Metadata,
78
PlanSubscriptionCreate,
89
PlanReferenceInput,
910
SubscriptionStatus,
@@ -26,19 +27,21 @@ async def main() -> None:
2627
print(f"Creating subscription for customer '{customer_key}' with plan '{plan_key}'...")
2728

2829
subscription_create = PlanSubscriptionCreate(
29-
customer_key=customer_key,
3030
plan=PlanReferenceInput(
3131
key=plan_key,
3232
),
3333
name="Free Plan Subscription",
3434
description="Subscription to the free plan for Acme Corporation",
35-
metadata={
36-
"source": "example",
37-
"environment": "development",
38-
},
35+
customer_key=customer_key,
36+
metadata=Metadata(
37+
{
38+
"source": "example",
39+
"environment": "development",
40+
}
41+
),
3942
)
4043

41-
subscription = await client.product_catalog.subscriptions.create(subscription_create)
44+
subscription = await client.subscriptions.create(subscription_create)
4245
print(f"Subscription created successfully with ID: {subscription.id}")
4346
print(f"Subscription name: {subscription.name}")
4447
print(f"Subscription status: {subscription.status}")
@@ -49,7 +52,7 @@ async def main() -> None:
4952
print(f"Billing cadence: {subscription.billing_cadence}")
5053

5154
# Retrieve the subscription to verify
52-
retrieved_subscription = await client.product_catalog.subscriptions.get_expanded(subscription.id)
55+
retrieved_subscription = await client.subscriptions.get_expanded(subscription.id)
5356
print(f"\nRetrieved subscription: {retrieved_subscription.name}")
5457
print(f"Status: {retrieved_subscription.status}")
5558
if retrieved_subscription.plan:
@@ -58,7 +61,7 @@ async def main() -> None:
5861

5962
# List subscriptions for the customer
6063
print(f"\nListing subscriptions for customer '{customer_key}'...")
61-
subscriptions_response = await client.customer.customers.list_customer_subscriptions(
64+
subscriptions_response = await client.customers.list_customer_subscriptions(
6265
customer_key, status=[SubscriptionStatus.ACTIVE]
6366
)
6467
for sub in subscriptions_response.items_property:

api/client/python/examples/poetry.lock

Lines changed: 36 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)