Skip to content

Add missing features and new endpoints#129

Open
wscourge wants to merge 8 commits intomainfrom
wiktor/pip-310-python-sdk-feature-updates
Open

Add missing features and new endpoints#129
wscourge wants to merge 8 commits intomainfrom
wiktor/pip-310-python-sdk-feature-updates

Conversation

@wscourge
Copy link
Copy Markdown
Contributor

@wscourge wscourge commented Mar 17, 2026

Summary

  • PIP-304: Expose errors field on LineItem and Transaction schemas (Invoice already had it)
  • PIP-120 / PIP-76: Add id, churn_recognition, churn_when_zero_mrr fields to Account schema; support include query param on /account
  • Invoice endpoints: Add Invoice.update_status (PATCH) and Invoice.disable (PATCH /invoices/{uuid}/disable)
  • SubscriptionEvent: Add destroy() / modify() wrappers that accept flat params instead of requiring subscription_event: {...} envelope; add disable() / enable() convenience methods

Backwards compatibility review

All changes are purely additive — no existing public API surface was modified or removed.

File Change Breaking?
account.py Added id, churn_recognition, churn_when_zero_mrr schema fields (allow_none=True) No — old responses without these fields still deserialize correctly
invoice.py Added errors field to LineItem._Schema (allow_none=True) No — additive field
invoice.py Added Invoice.update_status and Invoice.disable methods No — new methods only
transaction.py Added errors field to Transaction._Schema (allow_none=True) No — additive field
subscription_event.py Added disabled field to schema (allow_none=True) No — additive field
subscription_event.py Added destroy, modify, disable, enable classmethods No — new methods; existing destroy_with_params / modify_with_params unchanged
resource.py Added "disable" to uuid-required validation list No — only affects the new Invoice.disable method; no existing method used this name

Test plan

  • Existing 103 tests still pass
  • Added 27 new tests covering happy paths, error paths (404), edge cases (None/absent errors, envelope passthrough, no-mutation, missing uuid validation)
  • 130 total tests passing

Testing instructions

Integration tests were run against account WiktorOnboarding (acc_d0ea225e-f0f1-40ab-92cf-659dce5f2b76). To obtain the API key, impersonate wiktor@chartmogul.com in the admin panel and find it under Profile > API keys. The full test script is at sdks/tests/py/test_pip_310.py — set the CHARTMOGUL_API_KEY env var and run it.

cd sdks/py
pip3 install -e .
export CHARTMOGUL_API_KEY='...'
import chartmogul
config = chartmogul.Config(os.environ['CHARTMOGUL_API_KEY'])

Account.id field (PIP-120)

account = chartmogul.Account.retrieve(config).get()
assert account.id is not None and account.id.startswith('acc_')
print(f"Account ID: {account.id}")

Account include query param (PIP-76)

account = chartmogul.Account.retrieve(
    config,
    include='churn_recognition,churn_when_zero_mrr'
).get()
assert hasattr(account, 'churn_recognition')
assert hasattr(account, 'churn_when_zero_mrr')
print(f"churn_recognition={account.churn_recognition}, churn_when_zero_mrr={account.churn_when_zero_mrr}")

# Without include, fields should be absent
account_basic = chartmogul.Account.retrieve(config).get()
assert not hasattr(account_basic, 'churn_recognition')

LineItem and Transaction errors field (PIP-304)

invoice = chartmogul.Invoice.retrieve(
    config,
    uuid='inv_e81f78fc-00ab-4a16-95ab-d1f0cd9cd481',
    validation_type='all'
).get()

# Invoice-level errors accessible
print(f"invoice.errors = {getattr(invoice, 'errors', 'NOT SET')}")

# LineItem/Transaction schema supports errors field
# (API omits the key when no validation errors exist)
for li in invoice.line_items:
    print(f"li.errors = {getattr(li, 'errors', 'absent (no errors)')}")
for tx in invoice.transactions:
    print(f"tx.errors = {getattr(tx, 'errors', 'absent (no errors)')}")

Invoice.retrieve with query params

invoice = chartmogul.Invoice.retrieve(
    config,
    uuid='inv_e81f78fc-00ab-4a16-95ab-d1f0cd9cd481',
    validation_type='all',
    include_edit_histories=True,
    with_disabled=True,
).get()
assert invoice.uuid == 'inv_e81f78fc-00ab-4a16-95ab-d1f0cd9cd481'

Invoice.update_status

# Note: API returns 202 (async), SDK returns None
result = chartmogul.Invoice.update_status(
    config,
    uuid='inv_e81f78fc-00ab-4a16-95ab-d1f0cd9cd481',
    data={'disabled': True}
).get()
print(f"result = {result}")  # None (202 async)

# Re-enable
chartmogul.Invoice.update_status(
    config,
    uuid='inv_e81f78fc-00ab-4a16-95ab-d1f0cd9cd481',
    data={'disabled': False}
).get()

Invoice.disable

# Note: returns 404 for some invoices (e.g. Stripe-sourced); works for custom API invoices
try:
    result = chartmogul.Invoice.disable(
        config,
        uuid='inv_YOUR_CUSTOM_API_INVOICE'
    ).get()
    assert result.disabled is True
except chartmogul.APIError:
    print("404 — endpoint not available for this invoice type")

# Missing uuid raises ArgumentMissingError
try:
    chartmogul.Invoice.disable(config)
    assert False, "should have raised"
except chartmogul.ArgumentMissingError:
    print("Correctly raised ArgumentMissingError")

Invoice.all with query params

result = chartmogul.Invoice.all(config, validation_type='all', with_disabled=True).get()
assert len(result.invoices) > 0

result2 = chartmogul.Invoice.all(config, include_edit_histories=True).get()
assert len(result2.invoices) > 0

SubscriptionEvent.disabled schema field

sevs = chartmogul.SubscriptionEvent.all(config).get()
se = sevs.subscription_events[0]
assert isinstance(se.disabled, bool)
print(f"Event {se.id}: disabled={se.disabled}")

SubscriptionEvent.modify — flat params and envelope passthrough

# Flat params (auto-wrapped into subscription_event envelope)
se = chartmogul.SubscriptionEvent.modify(
    config, data={'id': 543978478}
).get()
assert se.id == 543978478

# Envelope passthrough (already wrapped — passed through as-is)
se = chartmogul.SubscriptionEvent.modify(
    config, data={'subscription_event': {'id': 543978478}}
).get()
assert se.id == 543978478

SubscriptionEvent.destroy — flat params and envelope passthrough

import time

# Create a temporary event
ts = int(time.time() * 1000)
se = chartmogul.SubscriptionEvent.create(config, data={
    'subscription_event': {
        'external_id': f'test_destroy_{ts}',
        'customer_external_id': 'test_customer_1769680554588',
        'data_source_uuid': 'ds_45d064ca-fcf8-11f0-903f-33618f80d753',
        'subscription_external_id': 'test_subscription_1769680554588',
        'event_type': 'subscription_cancelled',
        'event_date': '2026-03-25T00:00:00Z',
        'effective_date': '2026-03-25T00:00:00Z',
    }
}).get()

# Destroy with flat params
chartmogul.SubscriptionEvent.destroy(config, data={'id': se.id}).get()

# Also works with external_id + data_source_uuid, and envelope passthrough

SubscriptionEvent.disable and enable

# Disable with flat params
se = chartmogul.SubscriptionEvent.disable(
    config, data={'id': 543978478}
).get()
assert isinstance(se, chartmogul.SubscriptionEvent)

# Enable with flat params
se = chartmogul.SubscriptionEvent.enable(
    config, data={'id': 543978478}
).get()
assert isinstance(se, chartmogul.SubscriptionEvent)

# Works with external_id + data_source_uuid
se = chartmogul.SubscriptionEvent.disable(
    config,
    data={'external_id': 'test_debug_1774356733355', 'data_source_uuid': 'ds_45d064ca-fcf8-11f0-903f-33618f80d753'}
).get()

# Works with envelope passthrough
se = chartmogul.SubscriptionEvent.disable(
    config, data={'subscription_event': {'id': 543978478}}
).get()

# Re-enable to clean up
chartmogul.SubscriptionEvent.enable(config, data={'id': 543978478}).get()

🤖 Generated with Claude Code

wscourge and others added 5 commits March 17, 2026 17:04
Invoice already has an `errors` field, but LineItem and Transaction did
not. Add `errors = fields.Dict(allow_none=True)` to both schemas so
validation errors returned by the API are accessible on nested objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `id` field to Account schema so the account identifier is accessible.
Add `churn_recognition` and `churn_when_zero_mrr` fields to support the
optional `include` query parameter on the /account endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Invoice.update_status (PATCH /invoices/{uuid}) for updating invoice
status and Invoice.disable (PATCH /invoices/{uuid}/disable) for
disabling invoices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SubscriptionEvent.destroy() and .modify() that accept flat params
(e.g. data={"id": 123}) and auto-wrap in the subscription_event
envelope. The old _with_params methods are preserved for backwards
compatibility.

Add SubscriptionEvent.disable() and .enable() convenience methods
for toggling the disabled state of subscription events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add edge case, error path, and backwards-compatibility tests:

Invoice:
- Verify update_status request body is sent correctly
- 404 error paths for update_status and disable
- Verify disable sends no request body
- LineItem/Transaction errors=None and errors-absent cases

SubscriptionEvent:
- Flat destroy/modify with external_id+data_source_uuid
- Passthrough when caller already wraps in envelope (no double-wrap)
- disable/enable with external_id+data_source_uuid identification

Account:
- Graceful handling when id field absent from response
- Single include param

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wscourge wscourge marked this pull request as ready for review March 17, 2026 16:16
@wscourge wscourge requested a review from Copilot March 17, 2026 16:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Python SDK to support additional API response fields and adds/adjusts client methods for invoice and subscription event operations, with accompanying test coverage.

Changes:

  • Add deserialization support for errors on invoice line items/transactions and for additional account fields (id, churn settings).
  • Add invoice endpoints for updating status and disabling invoices.
  • Add SubscriptionEvent.destroy/modify/disable/enable helpers that accept “flat” params and wrap them in the required subscription_event envelope, plus tests for these behaviors.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
chartmogul/api/transaction.py Adds errors field support on Transaction schema.
chartmogul/api/invoice.py Adds errors support on invoice-related schemas and introduces update_status / disable methods.
chartmogul/api/account.py Extends Account schema with optional id and churn-related fields.
chartmogul/api/subscription_event.py Adds wrapper methods to accept flat params and to enable/disable subscription events.
test/api/test_invoice.py Adds tests for nested errors fields and new invoice status/disable endpoints.
test/api/test_account.py Adds tests for newly supported account fields and include query behavior.
test/api/test_subscription_event.py Adds tests for flat-parameter wrapping and enable/disable helpers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Invoice.disable: rename method from "patch" to "disable" and add
  "disable" to _validate_arguments uuid check so calling without uuid
  raises ArgumentMissingError instead of silently producing a bad URL

- SubscriptionEvent._disable/_enable: copy caller dict before mutation
  to avoid side effects; when payload is already wrapped in a
  subscription_event envelope, set the disabled flag inside it rather
  than at the top level

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@wscourge wscourge changed the title Wiktor/pip 310 python sdk feature updates Add missing SDK features (PIP-304, PIP-120, PIP-76) and new endpoints Mar 18, 2026
@wscourge wscourge changed the title Add missing SDK features (PIP-304, PIP-120, PIP-76) and new endpoints Add missing features and new endpoints Mar 20, 2026
wscourge and others added 2 commits March 24, 2026 13:25
…d field, clean up tests

- Move destroy/modify/disable/enable from module-scope into SubscriptionEvent as proper classmethods
- Add disabled field to SubscriptionEvent schema so API responses are fully deserialized
- Defensive dict() copy in destroy/modify for consistency with disable/enable
- Rename camelCase test fixtures to snake_case, reduce duplication via spread and helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API returns a boolean for churn_when_zero_mrr, not a string.
Using fields.Raw allows any JSON type to be deserialized correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants