Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
021294e
feat(api): api update
stainless-app[bot] Mar 18, 2026
ca495f7
fix: sanitize endpoint path params
stainless-app[bot] Mar 19, 2026
a391cfc
feat(api): api update
stainless-app[bot] Mar 20, 2026
e409b46
chore(internal): update gitignore
stainless-app[bot] Mar 23, 2026
f4459b0
feat(api): api update
stainless-app[bot] Mar 23, 2026
5428231
chore(ci): skip lint on metadata-only changes
stainless-app[bot] Mar 24, 2026
7842b8a
codegen metadata
stainless-app[bot] Mar 24, 2026
bd3dd9e
codegen metadata
stainless-app[bot] Mar 25, 2026
8f4b022
feat(api): api update
stainless-app[bot] Mar 25, 2026
a594def
codegen metadata
stainless-app[bot] Mar 25, 2026
f756c56
codegen metadata
stainless-app[bot] Mar 25, 2026
a34e3b1
codegen metadata
stainless-app[bot] Mar 25, 2026
a6443ea
feat(api): api update
stainless-app[bot] Apr 2, 2026
d2130a3
codegen metadata
stainless-app[bot] Mar 26, 2026
e8bc1d6
feat(internal): implement indices array format for query and form ser…
stainless-app[bot] Mar 26, 2026
349533c
feat(api): api update
stainless-app[bot] Mar 26, 2026
cea4dc2
feat(api): api update
stainless-app[bot] Mar 27, 2026
881d399
feat(api): api update
stainless-app[bot] Mar 27, 2026
91ad304
feat(api): api update
stainless-app[bot] Mar 27, 2026
94e743d
codegen metadata
stainless-app[bot] Mar 27, 2026
fddd0a4
codegen metadata
stainless-app[bot] Mar 28, 2026
5a8eec6
codegen metadata
stainless-app[bot] Mar 30, 2026
10c00d9
feat(api): api update
stainless-app[bot] Mar 30, 2026
82ba7a3
feat(api): api update
stainless-app[bot] Mar 31, 2026
e733ac9
feat(api): api update
stainless-app[bot] Mar 31, 2026
6ac4297
feat(api): api update
stainless-app[bot] Mar 31, 2026
c7b5425
feat(api): api update
stainless-app[bot] Apr 1, 2026
719e1f0
codegen metadata
stainless-app[bot] Apr 1, 2026
6aa464e
feat(api): api update
stainless-app[bot] Apr 1, 2026
82f0234
feat(api): api update
stainless-app[bot] Apr 1, 2026
014b6bb
feat(api): manual updates
stainless-app[bot] Apr 3, 2026
6f857b2
fix: add missing import and response wrappers for add_free_days method
jharrell Apr 3, 2026
09680cc
Merge pull request #489 from stainless-sdks/jon/fix-missing-add-free-…
cnrstvns Apr 3, 2026
c2694e3
feat(api): api update
stainless-app[bot] Apr 3, 2026
dfd2e5b
feat(api): api update
stainless-app[bot] Apr 4, 2026
f31d7e8
codegen metadata
stainless-app[bot] Apr 5, 2026
71e103e
release: 0.0.36
stainless-app[bot] Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/whopsdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@v6

Expand All @@ -38,7 +38,7 @@ jobs:
run: ./scripts/lint

build:
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
timeout-minutes: 10
name: build
permissions:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.prism.log
.stdy.log
_dev

__pycache__
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.0.35"
".": "0.0.36"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 191
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-97e920db587b07c064bef1bf31de5e110a3750ddfddc8fca26642e79e6b827f8.yml
openapi_spec_hash: 97ca16cc55271602443a4329d1e02895
config_hash: 1a836d20bb988f001cc66d1526f71306
configured_endpoints: 193
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e972fd41393b2edf6b6fddb7c52b14da652d4cff58399d5e7fc5b005081b85c1.yml
openapi_spec_hash: ca7941edec9fa06d9319dbb6fd0cc5c8
config_hash: fee5c8b2e5f00cef705ad48c3f3b5b5a
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# Changelog

## 0.0.36 (2026-04-05)

Full Changelog: [v0.0.35...v0.0.36](https://github.com/whopio/whopsdk-python/compare/v0.0.35...v0.0.36)

### Features

* **api:** api update ([dfd2e5b](https://github.com/whopio/whopsdk-python/commit/dfd2e5b1fde74bf5d93fa8195e3aa7610c8349e6))
* **api:** api update ([c2694e3](https://github.com/whopio/whopsdk-python/commit/c2694e3e189f52f64006c5b2503b901f31fcfe71))
* **api:** api update ([82f0234](https://github.com/whopio/whopsdk-python/commit/82f02347c686891b5ffaeacd7cd84b9bc0a314d8))
* **api:** api update ([6aa464e](https://github.com/whopio/whopsdk-python/commit/6aa464e34ab3d27134a66157a4ca0ef4d20b810a))
* **api:** api update ([c7b5425](https://github.com/whopio/whopsdk-python/commit/c7b5425e8257162625fd6db79079953247a05d90))
* **api:** api update ([6ac4297](https://github.com/whopio/whopsdk-python/commit/6ac4297d8b69e3f8209c69786f7017dd10913053))
* **api:** api update ([e733ac9](https://github.com/whopio/whopsdk-python/commit/e733ac94f3c0f71876b46145c325e597c9569541))
* **api:** api update ([82ba7a3](https://github.com/whopio/whopsdk-python/commit/82ba7a3c12417e18a6408a32eb2c4bf160e94768))
* **api:** api update ([10c00d9](https://github.com/whopio/whopsdk-python/commit/10c00d9e1c45709469a36ea63c624b04fa312e58))
* **api:** api update ([91ad304](https://github.com/whopio/whopsdk-python/commit/91ad304ea92ca8916afd9e6abb17d463b1b81b58))
* **api:** api update ([881d399](https://github.com/whopio/whopsdk-python/commit/881d399d3b01938fd43252b435935798bd3171f7))
* **api:** api update ([cea4dc2](https://github.com/whopio/whopsdk-python/commit/cea4dc2006ad8a1819e93fa2632edc511ab9731c))
* **api:** api update ([349533c](https://github.com/whopio/whopsdk-python/commit/349533cc0cdb665e00d6ca104292b8cf9ae4d0d0))
* **api:** api update ([a6443ea](https://github.com/whopio/whopsdk-python/commit/a6443ea4697c674dc80a9877fd9c531a6b688d73))
* **api:** api update ([8f4b022](https://github.com/whopio/whopsdk-python/commit/8f4b022372bee1a75d74e83334431e030ee02496))
* **api:** api update ([f4459b0](https://github.com/whopio/whopsdk-python/commit/f4459b0d9fa3d7e4e01db71c29611d98099dfadf))
* **api:** api update ([a391cfc](https://github.com/whopio/whopsdk-python/commit/a391cfca3a11e21d4ac80518369b44f8f6f9ee2f))
* **api:** api update ([021294e](https://github.com/whopio/whopsdk-python/commit/021294ecc577e24f56839d852bc978b839feb7fe))
* **api:** manual updates ([014b6bb](https://github.com/whopio/whopsdk-python/commit/014b6bba88788acb14ce8240388b8712209f66fa))
* **internal:** implement indices array format for query and form serialization ([e8bc1d6](https://github.com/whopio/whopsdk-python/commit/e8bc1d6948d253c722be8ea0802e8187165f4a0d))


### Bug Fixes

* add missing import and response wrappers for add_free_days method ([6f857b2](https://github.com/whopio/whopsdk-python/commit/6f857b22e4db5b371779e0759ddbd82752a0acc9))
* sanitize endpoint path params ([ca495f7](https://github.com/whopio/whopsdk-python/commit/ca495f7081478f0056efbad9c75eea30af62f9fe))


### Chores

* **ci:** skip lint on metadata-only changes ([5428231](https://github.com/whopio/whopsdk-python/commit/542823145fee2ead9f920efd00278464d8601513))
* **internal:** update gitignore ([e409b46](https://github.com/whopio/whopsdk-python/commit/e409b46665a918b487c9e10c25dd48819398f2bc))

## 0.0.35 (2026-03-18)

Full Changelog: [v0.0.34...v0.0.35](https://github.com/whopio/whopsdk-python/compare/v0.0.34...v0.0.35)
Expand Down
13 changes: 12 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,21 @@ Methods:
Types:

```python
from whop_sdk.types import InvoiceVoidResponse
from whop_sdk.types import (
TaxIdentifierType,
InvoiceMarkPaidResponse,
InvoiceMarkUncollectibleResponse,
InvoiceVoidResponse,
)
```

Methods:

- <code title="post /invoices">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">create</a>(\*\*<a href="src/whop_sdk/types/invoice_create_params.py">params</a>) -> <a href="./src/whop_sdk/types/shared/invoice.py">Invoice</a></code>
- <code title="get /invoices/{id}">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">retrieve</a>(id) -> <a href="./src/whop_sdk/types/shared/invoice.py">Invoice</a></code>
- <code title="get /invoices">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">list</a>(\*\*<a href="src/whop_sdk/types/invoice_list_params.py">params</a>) -> <a href="./src/whop_sdk/types/shared/invoice_list_item.py">SyncCursorPage[InvoiceListItem]</a></code>
- <code title="post /invoices/{id}/mark_paid">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">mark_paid</a>(id) -> <a href="./src/whop_sdk/types/invoice_mark_paid_response.py">InvoiceMarkPaidResponse</a></code>
- <code title="post /invoices/{id}/mark_uncollectible">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">mark_uncollectible</a>(id) -> <a href="./src/whop_sdk/types/invoice_mark_uncollectible_response.py">InvoiceMarkUncollectibleResponse</a></code>
- <code title="post /invoices/{id}/void">client.invoices.<a href="./src/whop_sdk/resources/invoices.py">void</a>(id) -> <a href="./src/whop_sdk/types/invoice_void_response.py">InvoiceVoidResponse</a></code>

# CourseLessonInteractions
Expand Down Expand Up @@ -144,6 +151,7 @@ from whop_sdk.types import (
WebhookListResponse,
WebhookDeleteResponse,
InvoiceCreatedWebhookEvent,
InvoiceMarkedUncollectibleWebhookEvent,
InvoicePaidWebhookEvent,
InvoicePastDueWebhookEvent,
InvoiceVoidedWebhookEvent,
Expand All @@ -162,6 +170,9 @@ from whop_sdk.types import (
PayoutMethodCreatedWebhookEvent,
VerificationSucceededWebhookEvent,
PayoutAccountStatusUpdatedWebhookEvent,
ResolutionCenterCaseCreatedWebhookEvent,
ResolutionCenterCaseUpdatedWebhookEvent,
ResolutionCenterCaseDecidedWebhookEvent,
PaymentCreatedWebhookEvent,
PaymentSucceededWebhookEvent,
PaymentFailedWebhookEvent,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "whop-sdk"
version = "0.0.35"
version = "0.0.36"
description = "The official Python library for the Whop API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
5 changes: 4 additions & 1 deletion src/whop_sdk/_qs.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ def _stringify_item(
items.extend(self._stringify_item(key, item, opts))
return items
elif array_format == "indices":
raise NotImplementedError("The array indices format is not supported yet")
items = []
for i, item in enumerate(value):
items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
return items
elif array_format == "brackets":
items = []
key = key + "[]"
Expand Down
1 change: 1 addition & 0 deletions src/whop_sdk/_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ._path import path_template as path_template
from ._sync import asyncify as asyncify
from ._proxy import LazyProxy as LazyProxy
from ._utils import (
Expand Down
127 changes: 127 additions & 0 deletions src/whop_sdk/_utils/_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from __future__ import annotations

import re
from typing import (
Any,
Mapping,
Callable,
)
from urllib.parse import quote

# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")

_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")


def _quote_path_segment_part(value: str) -> str:
"""Percent-encode `value` for use in a URI path segment.

Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
"""
# quote() already treats unreserved characters (letters, digits, and -._~)
# as safe, so we only need to add sub-delims, ':', and '@'.
# Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
return quote(value, safe="!$&'()*+,;=:@")


def _quote_query_part(value: str) -> str:
"""Percent-encode `value` for use in a URI query string.

Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
"""
return quote(value, safe="!$'()*+,;:@/?")


def _quote_fragment_part(value: str) -> str:
"""Percent-encode `value` for use in a URI fragment.

Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
"""
return quote(value, safe="!$&'()*+,;=:@/?")


def _interpolate(
template: str,
values: Mapping[str, Any],
quoter: Callable[[str], str],
) -> str:
"""Replace {name} placeholders in `template`, quoting each value with `quoter`.

Placeholder names are looked up in `values`.

Raises:
KeyError: If a placeholder is not found in `values`.
"""
# re.split with a capturing group returns alternating
# [text, name, text, name, ..., text] elements.
parts = _PLACEHOLDER_RE.split(template)

for i in range(1, len(parts), 2):
name = parts[i]
if name not in values:
raise KeyError(f"a value for placeholder {{{name}}} was not provided")
val = values[name]
if val is None:
parts[i] = "null"
elif isinstance(val, bool):
parts[i] = "true" if val else "false"
else:
parts[i] = quoter(str(values[name]))

return "".join(parts)


def path_template(template: str, /, **kwargs: Any) -> str:
"""Interpolate {name} placeholders in `template` from keyword arguments.

Args:
template: The template string containing {name} placeholders.
**kwargs: Keyword arguments to interpolate into the template.

Returns:
The template with placeholders interpolated and percent-encoded.

Safe characters for percent-encoding are dependent on the URI component.
Placeholders in path and fragment portions are percent-encoded where the `segment`
and `fragment` sets from RFC 3986 respectively are considered safe.
Placeholders in the query portion are percent-encoded where the `query` set from
RFC 3986 §3.3 is considered safe except for = and & characters.

Raises:
KeyError: If a placeholder is not found in `kwargs`.
ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
"""
# Split the template into path, query, and fragment portions.
fragment_template: str | None = None
query_template: str | None = None

rest = template
if "#" in rest:
rest, fragment_template = rest.split("#", 1)
if "?" in rest:
rest, query_template = rest.split("?", 1)
path_template = rest

# Interpolate each portion with the appropriate quoting rules.
path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)

# Reject dot-segments (. and ..) in the final assembled path. The check
# runs after interpolation so that adjacent placeholders or a mix of static
# text and placeholders that together form a dot-segment are caught.
# Also reject percent-encoded dot-segments to protect against incorrectly
# implemented normalization in servers/proxies.
for segment in path_result.split("/"):
if _DOT_SEGMENT_RE.match(segment):
raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")

result = path_result
if query_template is not None:
result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
if fragment_template is not None:
result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)

return result
2 changes: 1 addition & 1 deletion src/whop_sdk/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "whop_sdk"
__version__ = "0.0.35" # x-release-please-version
__version__ = "0.0.36" # x-release-please-version
14 changes: 7 additions & 7 deletions src/whop_sdk/resources/affiliates/affiliates.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from ...types import Status, affiliate_list_params, affiliate_create_params
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import maybe_transform, async_maybe_transform
from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from .overrides import (
OverridesResource,
Expand Down Expand Up @@ -142,7 +142,7 @@ def retrieve(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
f"/affiliates/{id}",
path_template("/affiliates/{id}", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -259,7 +259,7 @@ def archive(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._post(
f"/affiliates/{id}/archive",
path_template("/affiliates/{id}/archive", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -296,7 +296,7 @@ def unarchive(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._post(
f"/affiliates/{id}/unarchive",
path_template("/affiliates/{id}/unarchive", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -408,7 +408,7 @@ async def retrieve(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
f"/affiliates/{id}",
path_template("/affiliates/{id}", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -525,7 +525,7 @@ async def archive(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._post(
f"/affiliates/{id}/archive",
path_template("/affiliates/{id}/archive", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down Expand Up @@ -562,7 +562,7 @@ async def unarchive(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._post(
f"/affiliates/{id}/unarchive",
path_template("/affiliates/{id}/unarchive", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
Expand Down
Loading