From e27cde011cdda82c57f242acdd20ac7955619373 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:24:25 +0000 Subject: [PATCH 01/20] feat(api): api update --- .stats.yml | 4 ++-- .../types/ledger_account_retrieve_response.py | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index ff4a3e82..d388d252 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e8bcfd8a19f54624e644a1bc061df234a2e12af010a6c595d5a698cdd0e20682.yml -openapi_spec_hash: b47bb36fbc4c9ebdf3cf6b06f33356ef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-d4ae48a23ad0c5b6ab0650269aa7ee58fbe3ca9e54d2fb504c36f355abfd519c.yml +openapi_spec_hash: a33d3e97001446b1318de91d81afe60b config_hash: 131e5271eed10cf11ebc421d5a0c5f8a diff --git a/src/whop_sdk/types/ledger_account_retrieve_response.py b/src/whop_sdk/types/ledger_account_retrieve_response.py index faa910e2..042d20f3 100644 --- a/src/whop_sdk/types/ledger_account_retrieve_response.py +++ b/src/whop_sdk/types/ledger_account_retrieve_response.py @@ -62,7 +62,21 @@ class LedgerAccountRetrieveResponse(BaseModel): balances: List[Balance] """The balances associated with the account.""" - ledger_account_audit_status: Optional[Literal["reserves_imposed", "requested_more_information"]] = None + ledger_account_audit_status: Optional[ + Literal[ + "pending", + "approved", + "reserves_imposed", + "suspended", + "ignored", + "rejected", + "requested_more_information", + "information_submitted", + "requested_tos_violation_correction", + "clawback_attempted", + "awaiting_sales_review", + ] + ] = None """The different statuses a LedgerAccountAudit can be""" ledger_type: Literal["primary", "pool"] From b7f500dcaa5daa83d6d236e216f130fc6c4db29e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:50:35 +0000 Subject: [PATCH 02/20] feat(api): api update --- .stats.yml | 4 +- .../resources/checkout_configurations.py | 4 ++ src/whop_sdk/resources/products.py | 16 ----- .../checkout_configuration_create_params.py | 67 ++++++++++++++++++- src/whop_sdk/types/product_create_params.py | 25 +------ src/whop_sdk/types/shared/product.py | 8 +++ .../types/shared/product_list_item.py | 8 +++ .../test_checkout_configurations.py | 32 +++++++++ tests/api_resources/test_products.py | 10 --- 9 files changed, 120 insertions(+), 54 deletions(-) diff --git a/.stats.yml b/.stats.yml index d388d252..a8e95052 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-d4ae48a23ad0c5b6ab0650269aa7ee58fbe3ca9e54d2fb504c36f355abfd519c.yml -openapi_spec_hash: a33d3e97001446b1318de91d81afe60b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-24b3f49496f835b7d45e9ed125f970b0fdd56b89e437000860728eac1c08a75d.yml +openapi_spec_hash: abe8b7c338997ba7bb9cef031e836225 config_hash: 131e5271eed10cf11ebc421d5a0c5f8a diff --git a/src/whop_sdk/resources/checkout_configurations.py b/src/whop_sdk/resources/checkout_configurations.py index 756aaa60..4bb352ed 100644 --- a/src/whop_sdk/resources/checkout_configurations.py +++ b/src/whop_sdk/resources/checkout_configurations.py @@ -68,6 +68,8 @@ def create( - `checkout_configuration:create` - `plan:create` + - `access_pass:create` + - `access_pass:update` Args: affiliate_code: The affiliate code to use for the checkout configuration @@ -257,6 +259,8 @@ async def create( - `checkout_configuration:create` - `plan:create` + - `access_pass:create` + - `access_pass:update` Args: affiliate_code: The affiliate code to use for the checkout configuration diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index 0c9cba7c..b23f3f0e 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -60,8 +60,6 @@ def create( *, company_id: str, title: str, - access_pass_type: Optional[AccessPassType] | Omit = omit, - banner_image: Optional[product_create_params.BannerImage] | Omit = omit, business_type: Optional[BusinessTypes] | Omit = omit, collect_shipping_address: Optional[bool] | Omit = omit, custom_cta: Optional[CustomCta] | Omit = omit, @@ -101,10 +99,6 @@ def create( title: The title of the product. - access_pass_type: The different types an access pass can be. - - banner_image: A banner image for the product in png, jpeg format - business_type: The different business types a company can be. collect_shipping_address: Whether or not to collect shipping information at checkout from the customer. @@ -159,8 +153,6 @@ def create( { "company_id": company_id, "title": title, - "access_pass_type": access_pass_type, - "banner_image": banner_image, "business_type": business_type, "collect_shipping_address": collect_shipping_address, "custom_cta": custom_cta, @@ -485,8 +477,6 @@ async def create( *, company_id: str, title: str, - access_pass_type: Optional[AccessPassType] | Omit = omit, - banner_image: Optional[product_create_params.BannerImage] | Omit = omit, business_type: Optional[BusinessTypes] | Omit = omit, collect_shipping_address: Optional[bool] | Omit = omit, custom_cta: Optional[CustomCta] | Omit = omit, @@ -526,10 +516,6 @@ async def create( title: The title of the product. - access_pass_type: The different types an access pass can be. - - banner_image: A banner image for the product in png, jpeg format - business_type: The different business types a company can be. collect_shipping_address: Whether or not to collect shipping information at checkout from the customer. @@ -584,8 +570,6 @@ async def create( { "company_id": company_id, "title": title, - "access_pass_type": access_pass_type, - "banner_image": banner_image, "business_type": business_type, "collect_shipping_address": collect_shipping_address, "custom_cta": custom_cta, diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index 4d40fff3..aa88f58f 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -9,9 +9,12 @@ from .shared.tax_type import TaxType from .shared.plan_type import PlanType from .shared.visibility import Visibility +from .shared.business_types import BusinessTypes +from .shared.industry_types import IndustryTypes from .shared.release_method import ReleaseMethod +from .shared.global_affiliate_status import GlobalAffiliateStatus -__all__ = ["CheckoutConfigurationCreateParams", "Plan", "PlanCustomField", "PlanImage"] +__all__ = ["CheckoutConfigurationCreateParams", "Plan", "PlanCustomField", "PlanImage", "PlanProduct"] class CheckoutConfigurationCreateParams(TypedDict, total=False): @@ -67,6 +70,59 @@ class PlanImage(TypedDict, total=False): """ +class PlanProduct(TypedDict, total=False): + external_identifier: Required[str] + """A unique ID used to find or create a product. + + When provided during creation, we will look for an existing product with this + external identifier — if found, it will be updated; otherwise, a new product + will be created. + """ + + title: Required[str] + """The title of the product.""" + + business_type: Optional[BusinessTypes] + """The different business types a company can be.""" + + collect_shipping_address: Optional[bool] + """Whether or not to collect shipping information at checkout from the customer.""" + + custom_statement_descriptor: Optional[str] + """The custom statement descriptor for the product i.e. + + WHOP\\**SPORTS, must be between 5 and 22 characters, contain at least one letter, + and not contain any of the following characters: <, >, \\,, ', " + """ + + description: Optional[str] + """A written description of the product.""" + + global_affiliate_percentage: Optional[float] + """The percentage of the revenue that goes to the global affiliate program.""" + + global_affiliate_status: Optional[GlobalAffiliateStatus] + """The different statuses of the global affiliate program for an access pass.""" + + headline: Optional[str] + """The headline of the product.""" + + industry_type: Optional[IndustryTypes] + """The different industry types a company can be in.""" + + product_tax_code_id: Optional[str] + """The ID of the product tax code to apply to this product.""" + + redirect_purchase_url: Optional[str] + """The URL to redirect the customer to after a purchase.""" + + route: Optional[str] + """The route of the product.""" + + visibility: Optional[Visibility] + """Visibility of a resource""" + + class Plan(TypedDict, total=False): company_id: Required[str] """The company the plan should be created for.""" @@ -110,8 +166,15 @@ class Plan(TypedDict, total=False): plan_type: Optional[PlanType] """The type of plan that can be attached to an access pass""" + product: Optional[PlanProduct] + """Pass this object to create a new product for this plan. + + We will use the product external identifier to find or create an existing + product. + """ + product_id: Optional[str] - """The product the plan is related to.""" + """The product the plan is related to. Either this or product is required.""" release_method: Optional[ReleaseMethod] """The methods of how a plan can be released.""" diff --git a/src/whop_sdk/types/product_create_params.py b/src/whop_sdk/types/product_create_params.py index f1b6de65..3e5f5c73 100644 --- a/src/whop_sdk/types/product_create_params.py +++ b/src/whop_sdk/types/product_create_params.py @@ -13,10 +13,9 @@ from .shared.business_types import BusinessTypes from .shared.industry_types import IndustryTypes from .shared.release_method import ReleaseMethod -from .shared.access_pass_type import AccessPassType from .shared.global_affiliate_status import GlobalAffiliateStatus -__all__ = ["ProductCreateParams", "BannerImage", "PlanOptions", "PlanOptionsCustomField", "ProductHighlight"] +__all__ = ["ProductCreateParams", "PlanOptions", "PlanOptionsCustomField", "ProductHighlight"] class ProductCreateParams(TypedDict, total=False): @@ -26,12 +25,6 @@ class ProductCreateParams(TypedDict, total=False): title: Required[str] """The title of the product.""" - access_pass_type: Optional[AccessPassType] - """The different types an access pass can be.""" - - banner_image: Optional[BannerImage] - """A banner image for the product in png, jpeg format""" - business_type: Optional[BusinessTypes] """The different business types a company can be.""" @@ -94,22 +87,6 @@ class ProductCreateParams(TypedDict, total=False): """Visibility of a resource""" -class BannerImage(TypedDict, total=False): - id: Optional[str] - """The ID of an existing attachment object. - - Use this when updating a resource and keeping a subset of the attachments. Don't - use this unless you know what you're doing. - """ - - direct_upload_id: Optional[str] - """This ID should be used the first time you upload an attachment. - - It is the ID of the direct upload that was created when uploading the file to S3 - via the mediaDirectUpload mutation. - """ - - class PlanOptionsCustomField(TypedDict, total=False): field_type: Required[Literal["text"]] """The type of the custom field.""" diff --git a/src/whop_sdk/types/shared/product.py b/src/whop_sdk/types/shared/product.py index 5db38e08..da8bfcf9 100644 --- a/src/whop_sdk/types/shared/product.py +++ b/src/whop_sdk/types/shared/product.py @@ -72,6 +72,14 @@ class Product(BaseModel): description: Optional[str] = None """A short description of what the company offers or does.""" + external_identifier: Optional[str] = None + """A unique identifier used to create or update products. + + When provided on product creation endpoints, we’ll look up an existing product + by this identifier — if it exists, we’ll update it; if not, we’ll create a new + one. + """ + global_affiliate_percentage: Optional[float] = None """ The percentage of a transaction a user is eligible to earn from the whop diff --git a/src/whop_sdk/types/shared/product_list_item.py b/src/whop_sdk/types/shared/product_list_item.py index c15dbb30..adc56a56 100644 --- a/src/whop_sdk/types/shared/product_list_item.py +++ b/src/whop_sdk/types/shared/product_list_item.py @@ -21,6 +21,14 @@ class ProductListItem(BaseModel): created_at: datetime """When the product was created.""" + external_identifier: Optional[str] = None + """A unique identifier used to create or update products. + + When provided on product creation endpoints, we’ll look up an existing product + by this identifier — if it exists, we’ll update it; if not, we’ll create a new + one. + """ + headline: Optional[str] = None """The headline of the product.""" diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index 13ad6753..b6401c2c 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -58,6 +58,22 @@ def test_method_create_with_all_params(self, client: Whop) -> None: "internal_notes": "internal_notes", "override_tax_type": "inclusive", "plan_type": "renewal", + "product": { + "external_identifier": "external_identifier", + "title": "title", + "business_type": "education_program", + "collect_shipping_address": True, + "custom_statement_descriptor": "custom_statement_descriptor", + "description": "description", + "global_affiliate_percentage": 6.9, + "global_affiliate_status": "enabled", + "headline": "headline", + "industry_type": "trading", + "product_tax_code_id": "ptc_xxxxxxxxxxxxxx", + "redirect_purchase_url": "redirect_purchase_url", + "route": "route", + "visibility": "visible", + }, "product_id": "prod_xxxxxxxxxxxxx", "release_method": "buy_now", "renewal_price": 6.9, @@ -233,6 +249,22 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N "internal_notes": "internal_notes", "override_tax_type": "inclusive", "plan_type": "renewal", + "product": { + "external_identifier": "external_identifier", + "title": "title", + "business_type": "education_program", + "collect_shipping_address": True, + "custom_statement_descriptor": "custom_statement_descriptor", + "description": "description", + "global_affiliate_percentage": 6.9, + "global_affiliate_status": "enabled", + "headline": "headline", + "industry_type": "trading", + "product_tax_code_id": "ptc_xxxxxxxxxxxxxx", + "redirect_purchase_url": "redirect_purchase_url", + "route": "route", + "visibility": "visible", + }, "product_id": "prod_xxxxxxxxxxxxx", "release_method": "buy_now", "renewal_price": 6.9, diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index 2d66be66..6238fdc4 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -36,11 +36,6 @@ def test_method_create_with_all_params(self, client: Whop) -> None: product = client.products.create( company_id="biz_xxxxxxxxxxxxxx", title="title", - access_pass_type="regular", - banner_image={ - "id": "id", - "direct_upload_id": "direct_upload_id", - }, business_type="education_program", collect_shipping_address=True, custom_cta="get_access", @@ -341,11 +336,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N product = await async_client.products.create( company_id="biz_xxxxxxxxxxxxxx", title="title", - access_pass_type="regular", - banner_image={ - "id": "id", - "direct_upload_id": "direct_upload_id", - }, business_type="education_program", collect_shipping_address=True, custom_cta="get_access", From 986b31c8e8ddc6f621c8e737eeb5ba24be4ea648 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:24:30 +0000 Subject: [PATCH 03/20] feat(api): api update --- .stats.yml | 4 +-- src/whop_sdk/resources/plans.py | 40 +++++++++++++++++++++ src/whop_sdk/resources/products.py | 8 +++++ src/whop_sdk/types/plan_create_params.py | 12 +++++++ src/whop_sdk/types/plan_update_params.py | 12 +++++++ src/whop_sdk/types/product_update_params.py | 13 ++++++- tests/api_resources/test_plans.py | 8 +++++ tests/api_resources/test_products.py | 8 +++++ 8 files changed, 102 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index a8e95052..58e3afa6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-24b3f49496f835b7d45e9ed125f970b0fdd56b89e437000860728eac1c08a75d.yml -openapi_spec_hash: abe8b7c338997ba7bb9cef031e836225 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-49a32e2d416428fdfd7981c067fbd46b212f02e253f4d4c5a06ef869a93cf59e.yml +openapi_spec_hash: 430ff40c8b6332f491e5ac307e0b1d84 config_hash: 131e5271eed10cf11ebc421d5a0c5f8a diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 873be659..4e894fb4 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -71,6 +71,8 @@ def create( plan_type: Optional[PlanType] | Omit = omit, release_method: Optional[ReleaseMethod] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + strike_through_initial_price: Optional[float] | Omit = omit, + strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, @@ -123,6 +125,12 @@ def create( renewal_price: The amount the customer is charged every billing period. Use only if a recurring payment. Provided as a number in dollars. Eg: 10.43 for $10.43 + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + + strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + title: The title of the plan. This will be visible on the product page to customers. trial_period_days: The number of free trial days added before a renewal plan. @@ -155,6 +163,8 @@ def create( "plan_type": plan_type, "release_method": release_method, "renewal_price": renewal_price, + "strike_through_initial_price": strike_through_initial_price, + "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, "visibility": visibility, @@ -219,6 +229,8 @@ def update( offer_cancel_discount: Optional[bool] | Omit = omit, override_tax_type: Optional[TaxType] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + strike_through_initial_price: Optional[float] | Omit = omit, + strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, @@ -262,6 +274,12 @@ def update( renewal_price: The amount the customer is charged every billing period. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + + strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + title: The title of the plan. This will be visible on the product page to customers. trial_period_days: The number of free trial days added before a renewal plan. @@ -293,6 +311,8 @@ def update( "offer_cancel_discount": offer_cancel_discount, "override_tax_type": override_tax_type, "renewal_price": renewal_price, + "strike_through_initial_price": strike_through_initial_price, + "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, "visibility": visibility, @@ -468,6 +488,8 @@ async def create( plan_type: Optional[PlanType] | Omit = omit, release_method: Optional[ReleaseMethod] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + strike_through_initial_price: Optional[float] | Omit = omit, + strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, @@ -520,6 +542,12 @@ async def create( renewal_price: The amount the customer is charged every billing period. Use only if a recurring payment. Provided as a number in dollars. Eg: 10.43 for $10.43 + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + + strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + title: The title of the plan. This will be visible on the product page to customers. trial_period_days: The number of free trial days added before a renewal plan. @@ -552,6 +580,8 @@ async def create( "plan_type": plan_type, "release_method": release_method, "renewal_price": renewal_price, + "strike_through_initial_price": strike_through_initial_price, + "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, "visibility": visibility, @@ -616,6 +646,8 @@ async def update( offer_cancel_discount: Optional[bool] | Omit = omit, override_tax_type: Optional[TaxType] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + strike_through_initial_price: Optional[float] | Omit = omit, + strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, @@ -659,6 +691,12 @@ async def update( renewal_price: The amount the customer is charged every billing period. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + + strike_through_renewal_price: The price to display with a strikethrough for the renewal price. Provided as a + number in dollars. Eg: 19.99 for $19.99 + title: The title of the plan. This will be visible on the product page to customers. trial_period_days: The number of free trial days added before a renewal plan. @@ -690,6 +728,8 @@ async def update( "offer_cancel_discount": offer_cancel_discount, "override_tax_type": override_tax_type, "renewal_price": renewal_price, + "strike_through_initial_price": strike_through_initial_price, + "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, "visibility": visibility, diff --git a/src/whop_sdk/resources/products.py b/src/whop_sdk/resources/products.py index b23f3f0e..429bf9d8 100644 --- a/src/whop_sdk/resources/products.py +++ b/src/whop_sdk/resources/products.py @@ -238,6 +238,7 @@ def update( product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, + store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, title: Optional[str] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -290,6 +291,8 @@ def update( route: The route of the product. + store_page_config: Configuration for a product on the company's store page. + title: The title of the product. visibility: Visibility of a resource @@ -324,6 +327,7 @@ def update( "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, + "store_page_config": store_page_config, "title": title, "visibility": visibility, }, @@ -655,6 +659,7 @@ async def update( product_tax_code_id: Optional[str] | Omit = omit, redirect_purchase_url: Optional[str] | Omit = omit, route: Optional[str] | Omit = omit, + store_page_config: Optional[product_update_params.StorePageConfig] | Omit = omit, title: Optional[str] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -707,6 +712,8 @@ async def update( route: The route of the product. + store_page_config: Configuration for a product on the company's store page. + title: The title of the product. visibility: Visibility of a resource @@ -741,6 +748,7 @@ async def update( "product_tax_code_id": product_tax_code_id, "redirect_purchase_url": redirect_purchase_url, "route": route, + "store_page_config": store_page_config, "title": title, "visibility": visibility, }, diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 04aee462..237213bf 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -68,6 +68,18 @@ class PlanCreateParams(TypedDict, total=False): $10.43 """ + strike_through_initial_price: Optional[float] + """The price to display with a strikethrough for the initial price. + + Provided as a number in dollars. Eg: 19.99 for $19.99 + """ + + strike_through_renewal_price: Optional[float] + """The price to display with a strikethrough for the renewal price. + + Provided as a number in dollars. Eg: 19.99 for $19.99 + """ + title: Optional[str] """The title of the plan. This will be visible on the product page to customers.""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 5a2fd62d..c47dc1a3 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -49,6 +49,18 @@ class PlanUpdateParams(TypedDict, total=False): renewal_price: Optional[float] """The amount the customer is charged every billing period.""" + strike_through_initial_price: Optional[float] + """The price to display with a strikethrough for the initial price. + + Provided as a number in dollars. Eg: 19.99 for $19.99 + """ + + strike_through_renewal_price: Optional[float] + """The price to display with a strikethrough for the renewal price. + + Provided as a number in dollars. Eg: 19.99 for $19.99 + """ + title: Optional[str] """The title of the plan. This will be visible on the product page to customers.""" diff --git a/src/whop_sdk/types/product_update_params.py b/src/whop_sdk/types/product_update_params.py index fc0b6524..8251a5ca 100644 --- a/src/whop_sdk/types/product_update_params.py +++ b/src/whop_sdk/types/product_update_params.py @@ -11,7 +11,7 @@ from .shared.industry_types import IndustryTypes from .shared.global_affiliate_status import GlobalAffiliateStatus -__all__ = ["ProductUpdateParams", "BannerImage"] +__all__ = ["ProductUpdateParams", "BannerImage", "StorePageConfig"] class ProductUpdateParams(TypedDict, total=False): @@ -67,6 +67,9 @@ class ProductUpdateParams(TypedDict, total=False): route: Optional[str] """The route of the product.""" + store_page_config: Optional[StorePageConfig] + """Configuration for a product on the company's store page.""" + title: Optional[str] """The title of the product.""" @@ -88,3 +91,11 @@ class BannerImage(TypedDict, total=False): It is the ID of the direct upload that was created when uploading the file to S3 via the mediaDirectUpload mutation. """ + + +class StorePageConfig(TypedDict, total=False): + custom_cta: Optional[str] + """Custom call-to-action text for the product's store page.""" + + show_price: Optional[bool] + """Whether or not to show the price on the product's store page.""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index b86a579f..4e5a1d5a 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -61,6 +61,8 @@ def test_method_create_with_all_params(self, client: Whop) -> None: plan_type="renewal", release_method="buy_now", renewal_price=6.9, + strike_through_initial_price=6.9, + strike_through_renewal_price=6.9, title="title", trial_period_days=42, visibility="visible", @@ -173,6 +175,8 @@ def test_method_update_with_all_params(self, client: Whop) -> None: offer_cancel_discount=True, override_tax_type="inclusive", renewal_price=6.9, + strike_through_initial_price=6.9, + strike_through_renewal_price=6.9, title="title", trial_period_days=42, visibility="visible", @@ -352,6 +356,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N plan_type="renewal", release_method="buy_now", renewal_price=6.9, + strike_through_initial_price=6.9, + strike_through_renewal_price=6.9, title="title", trial_period_days=42, visibility="visible", @@ -464,6 +470,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N offer_cancel_discount=True, override_tax_type="inclusive", renewal_price=6.9, + strike_through_initial_price=6.9, + strike_through_renewal_price=6.9, title="title", trial_period_days=42, visibility="visible", diff --git a/tests/api_resources/test_products.py b/tests/api_resources/test_products.py index 6238fdc4..5b292411 100644 --- a/tests/api_resources/test_products.py +++ b/tests/api_resources/test_products.py @@ -184,6 +184,10 @@ def test_method_update_with_all_params(self, client: Whop) -> None: product_tax_code_id="ptc_xxxxxxxxxxxxxx", redirect_purchase_url="redirect_purchase_url", route="route", + store_page_config={ + "custom_cta": "custom_cta", + "show_price": True, + }, title="title", visibility="visible", ) @@ -484,6 +488,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N product_tax_code_id="ptc_xxxxxxxxxxxxxx", redirect_purchase_url="redirect_purchase_url", route="route", + store_page_config={ + "custom_cta": "custom_cta", + "show_price": True, + }, title="title", visibility="visible", ) From 4d649cf643820cbe60a4847c5eacba760d3b90ef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:24:29 +0000 Subject: [PATCH 04/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/plans.py | 4 ++-- src/whop_sdk/types/forum_post_list_response.py | 7 +++++++ src/whop_sdk/types/membership_list_response.py | 13 ++++++++++++- src/whop_sdk/types/payment_list_response.py | 4 ++-- src/whop_sdk/types/plan_create_params.py | 2 +- src/whop_sdk/types/shared/forum_post.py | 7 +++++++ src/whop_sdk/types/shared/invoice.py | 5 ++++- src/whop_sdk/types/shared/invoice_list_item.py | 5 ++++- src/whop_sdk/types/shared/membership.py | 13 ++++++++++++- src/whop_sdk/types/shared/payment.py | 4 ++-- 11 files changed, 55 insertions(+), 13 deletions(-) diff --git a/.stats.yml b/.stats.yml index 58e3afa6..2f695762 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-49a32e2d416428fdfd7981c067fbd46b212f02e253f4d4c5a06ef869a93cf59e.yml -openapi_spec_hash: 430ff40c8b6332f491e5ac307e0b1d84 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-3d9250360d6b999c841630d226265f9e52d6ba05878e3c77b139fd06fb6d058f.yml +openapi_spec_hash: 6d31d6f65cb686a551612bf48a031b48 config_hash: 131e5271eed10cf11ebc421d5a0c5f8a diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 4e894fb4..8327ca2e 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -97,7 +97,7 @@ def create( product_id: The product the plan is related to. - billing_period: The interval at which the plan charges (renewal plans). + billing_period: The interval in days at which the plan charges (renewal plans). currency: The available currencies on the platform @@ -514,7 +514,7 @@ async def create( product_id: The product the plan is related to. - billing_period: The interval at which the plan charges (renewal plans). + billing_period: The interval in days at which the plan charges (renewal plans). currency: The available currencies on the platform diff --git a/src/whop_sdk/types/forum_post_list_response.py b/src/whop_sdk/types/forum_post_list_response.py index 4ccfb1d2..7749d8dc 100644 --- a/src/whop_sdk/types/forum_post_list_response.py +++ b/src/whop_sdk/types/forum_post_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from .._models import BaseModel @@ -28,6 +29,9 @@ class ForumPostListResponse(BaseModel): content: Optional[str] = None """The content of the forum post in Markdown format""" + created_at: datetime + """The timestamp when the post was created""" + is_edited: bool """Whether the forum post has been edited""" @@ -46,6 +50,9 @@ class ForumPostListResponse(BaseModel): title: Optional[str] = None """The title of the forum post""" + updated_at: datetime + """The timestamp when the post was last updated""" + user: User """The user who created this forum post""" diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py index 178a081f..ce811ae0 100644 --- a/src/whop_sdk/types/membership_list_response.py +++ b/src/whop_sdk/types/membership_list_response.py @@ -7,7 +7,7 @@ from .shared.currency import Currency from .shared.membership_status import MembershipStatus -__all__ = ["MembershipListResponse", "Company", "Member", "Plan", "PromoCode", "User"] +__all__ = ["MembershipListResponse", "Company", "Member", "Plan", "Product", "PromoCode", "User"] class Company(BaseModel): @@ -28,6 +28,14 @@ class Plan(BaseModel): """The internal ID of the plan.""" +class Product(BaseModel): + id: str + """The internal ID of the public product.""" + + title: str + """The title of the product. Use for Whop 4.0.""" + + class PromoCode(BaseModel): id: str """The ID of the promo.""" @@ -92,6 +100,9 @@ class MembershipListResponse(BaseModel): plan: Plan """The Plan this Membership is for.""" + product: Product + """The Product this Membership grants access to.""" + promo_code: Optional[PromoCode] = None """The Promo Code that is currently applied to this Membership.""" diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 61cf5f2a..085b75f9 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -103,7 +103,7 @@ class PromoCode(BaseModel): """The specific code used to apply the promo at checkout.""" number_of_intervals: Optional[int] = None - """The number of billing cycles the promo is applied for.""" + """The number of months the promo is applied for.""" promo_type: PromoType """The type (% or flat amount) of the promo.""" @@ -197,7 +197,7 @@ class PaymentListResponse(BaseModel): """When the payment was refunded (if applicable).""" retryable: bool - """Whether the payment can be retried.""" + """A payment can be retried if the associated membership is past due""" status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index 237213bf..d3557ce8 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -22,7 +22,7 @@ class PlanCreateParams(TypedDict, total=False): """The product the plan is related to.""" billing_period: Optional[int] - """The interval at which the plan charges (renewal plans).""" + """The interval in days at which the plan charges (renewal plans).""" currency: Optional[Currency] """The available currencies on the platform""" diff --git a/src/whop_sdk/types/shared/forum_post.py b/src/whop_sdk/types/shared/forum_post.py index 7ad4855e..9156694f 100644 --- a/src/whop_sdk/types/shared/forum_post.py +++ b/src/whop_sdk/types/shared/forum_post.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from ..._models import BaseModel @@ -28,6 +29,9 @@ class ForumPost(BaseModel): content: Optional[str] = None """The content of the forum post in Markdown format""" + created_at: datetime + """The timestamp when the post was created""" + is_edited: bool """Whether the forum post has been edited""" @@ -46,6 +50,9 @@ class ForumPost(BaseModel): title: Optional[str] = None """The title of the forum post""" + updated_at: datetime + """The timestamp when the post was last updated""" + user: User """The user who created this forum post""" diff --git a/src/whop_sdk/types/shared/invoice.py b/src/whop_sdk/types/shared/invoice.py index 597d63cc..e4372cf6 100644 --- a/src/whop_sdk/types/shared/invoice.py +++ b/src/whop_sdk/types/shared/invoice.py @@ -49,7 +49,10 @@ class Invoice(BaseModel): """The email address that the invoice was created for.""" fetch_invoice_token: str - """The token to fetch the invoice.""" + """ + A signed token that allows fetching the invoice data publically without being + authenticated. + """ number: str """The number of the invoice.""" diff --git a/src/whop_sdk/types/shared/invoice_list_item.py b/src/whop_sdk/types/shared/invoice_list_item.py index 2d739aae..2763ceee 100644 --- a/src/whop_sdk/types/shared/invoice_list_item.py +++ b/src/whop_sdk/types/shared/invoice_list_item.py @@ -49,7 +49,10 @@ class InvoiceListItem(BaseModel): """The email address that the invoice was created for.""" fetch_invoice_token: str - """The token to fetch the invoice.""" + """ + A signed token that allows fetching the invoice data publically without being + authenticated. + """ number: str """The number of the invoice.""" diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py index d1df5310..81f8926c 100644 --- a/src/whop_sdk/types/shared/membership.py +++ b/src/whop_sdk/types/shared/membership.py @@ -7,7 +7,7 @@ from ..._models import BaseModel from .membership_status import MembershipStatus -__all__ = ["Membership", "Company", "Member", "Plan", "PromoCode", "User"] +__all__ = ["Membership", "Company", "Member", "Plan", "Product", "PromoCode", "User"] class Company(BaseModel): @@ -28,6 +28,14 @@ class Plan(BaseModel): """The internal ID of the plan.""" +class Product(BaseModel): + id: str + """The internal ID of the public product.""" + + title: str + """The title of the product. Use for Whop 4.0.""" + + class PromoCode(BaseModel): id: str """The ID of the promo.""" @@ -92,6 +100,9 @@ class Membership(BaseModel): plan: Plan """The Plan this Membership is for.""" + product: Product + """The Product this Membership grants access to.""" + promo_code: Optional[PromoCode] = None """The Promo Code that is currently applied to this Membership.""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index d84426f5..57ceaaba 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -93,7 +93,7 @@ class PromoCode(BaseModel): """The specific code used to apply the promo at checkout.""" number_of_intervals: Optional[int] = None - """The number of billing cycles the promo is applied for.""" + """The number of months the promo is applied for.""" promo_type: PromoType """The type (% or flat amount) of the promo.""" @@ -187,7 +187,7 @@ class Payment(BaseModel): """When the payment was refunded (if applicable).""" retryable: bool - """Whether the payment can be retried.""" + """A payment can be retried if the associated membership is past due""" status: Optional[ReceiptStatus] = None """The status of a receipt""" From 250083b2d774375af85f3e324002644dd4a3721a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:24:29 +0000 Subject: [PATCH 05/20] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2f695762..8b17b036 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-3d9250360d6b999c841630d226265f9e52d6ba05878e3c77b139fd06fb6d058f.yml -openapi_spec_hash: 6d31d6f65cb686a551612bf48a031b48 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-5937121891d6e322da3b24bf6741c1f8e58da1ba166ae3f5650e1b0c64bea344.yml +openapi_spec_hash: c4e8100ac5017d7963cf4e1d078ce291 config_hash: 131e5271eed10cf11ebc421d5a0c5f8a From 56d177cb6c896567c176a29847ff249b518ed41e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:24:24 +0000 Subject: [PATCH 06/20] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8b17b036..865f8237 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 81 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-5937121891d6e322da3b24bf6741c1f8e58da1ba166ae3f5650e1b0c64bea344.yml -openapi_spec_hash: c4e8100ac5017d7963cf4e1d078ce291 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-cc2c9659646bfa86078ad117aecdb4ad84f1dd6568cf30feb1fcbea0d99794e0.yml +openapi_spec_hash: f1c1d935bb1ab8adf977ca106da721ef config_hash: 131e5271eed10cf11ebc421d5a0c5f8a From c41adf99358901411a5947d09a691ab0f1d83f99 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:32:39 +0000 Subject: [PATCH 07/20] feat(api): manual updates --- .stats.yml | 4 +- api.md | 79 ++ src/whop_sdk/_client.py | 36 + src/whop_sdk/resources/__init__.py | 56 ++ src/whop_sdk/resources/course_chapters.py | 602 +++++++++++++++ src/whop_sdk/resources/course_lessons.py | 722 ++++++++++++++++++ src/whop_sdk/resources/courses.py | 697 +++++++++++++++++ src/whop_sdk/resources/experiences.py | 91 +++ src/whop_sdk/resources/forum_posts.py | 124 ++- src/whop_sdk/resources/messages.py | 114 ++- src/whop_sdk/resources/promo_codes.py | 652 ++++++++++++++++ src/whop_sdk/types/__init__.py | 32 + .../types/assessment_question_types.py | 7 + src/whop_sdk/types/course.py | 102 +++ src/whop_sdk/types/course_chapter.py | 32 + .../types/course_chapter_create_params.py | 16 + .../types/course_chapter_delete_response.py | 7 + .../types/course_chapter_list_params.py | 25 + .../types/course_chapter_list_response.py | 16 + .../types/course_chapter_update_params.py | 12 + src/whop_sdk/types/course_create_params.py | 38 + src/whop_sdk/types/course_delete_response.py | 7 + .../types/course_lesson_create_params.py | 27 + .../types/course_lesson_delete_response.py | 7 + .../types/course_lesson_list_params.py | 28 + .../types/course_lesson_list_response.py | 35 + .../types/course_lesson_update_params.py | 144 ++++ src/whop_sdk/types/course_list_params.py | 28 + src/whop_sdk/types/course_list_response.py | 70 ++ src/whop_sdk/types/course_update_params.py | 86 +++ .../types/experience_duplicate_params.py | 13 + .../types/forum_post_update_params.py | 41 + src/whop_sdk/types/languages.py | 30 + src/whop_sdk/types/lesson.py | 159 ++++ src/whop_sdk/types/lesson_types.py | 7 + src/whop_sdk/types/lesson_visibilities.py | 7 + src/whop_sdk/types/message_update_params.py | 35 + src/whop_sdk/types/promo_code.py | 90 +++ .../types/promo_code_create_params.py | 70 ++ .../types/promo_code_delete_response.py | 7 + src/whop_sdk/types/promo_code_list_params.py | 37 + .../types/promo_code_list_response.py | 79 ++ src/whop_sdk/types/promo_code_status.py | 7 + src/whop_sdk/types/promo_duration.py | 7 + src/whop_sdk/types/shared_params/__init__.py | 1 + .../types/shared_params/promo_type.py | 9 + tests/api_resources/test_course_chapters.py | 467 +++++++++++ tests/api_resources/test_course_lessons.py | 547 +++++++++++++ tests/api_resources/test_courses.py | 531 +++++++++++++ tests/api_resources/test_experiences.py | 102 +++ tests/api_resources/test_forum_posts.py | 122 ++- tests/api_resources/test_messages.py | 120 ++- tests/api_resources/test_promo_codes.py | 444 +++++++++++ 53 files changed, 6822 insertions(+), 6 deletions(-) create mode 100644 src/whop_sdk/resources/course_chapters.py create mode 100644 src/whop_sdk/resources/course_lessons.py create mode 100644 src/whop_sdk/resources/courses.py create mode 100644 src/whop_sdk/resources/promo_codes.py create mode 100644 src/whop_sdk/types/assessment_question_types.py create mode 100644 src/whop_sdk/types/course.py create mode 100644 src/whop_sdk/types/course_chapter.py create mode 100644 src/whop_sdk/types/course_chapter_create_params.py create mode 100644 src/whop_sdk/types/course_chapter_delete_response.py create mode 100644 src/whop_sdk/types/course_chapter_list_params.py create mode 100644 src/whop_sdk/types/course_chapter_list_response.py create mode 100644 src/whop_sdk/types/course_chapter_update_params.py create mode 100644 src/whop_sdk/types/course_create_params.py create mode 100644 src/whop_sdk/types/course_delete_response.py create mode 100644 src/whop_sdk/types/course_lesson_create_params.py create mode 100644 src/whop_sdk/types/course_lesson_delete_response.py create mode 100644 src/whop_sdk/types/course_lesson_list_params.py create mode 100644 src/whop_sdk/types/course_lesson_list_response.py create mode 100644 src/whop_sdk/types/course_lesson_update_params.py create mode 100644 src/whop_sdk/types/course_list_params.py create mode 100644 src/whop_sdk/types/course_list_response.py create mode 100644 src/whop_sdk/types/course_update_params.py create mode 100644 src/whop_sdk/types/experience_duplicate_params.py create mode 100644 src/whop_sdk/types/forum_post_update_params.py create mode 100644 src/whop_sdk/types/languages.py create mode 100644 src/whop_sdk/types/lesson.py create mode 100644 src/whop_sdk/types/lesson_types.py create mode 100644 src/whop_sdk/types/lesson_visibilities.py create mode 100644 src/whop_sdk/types/message_update_params.py create mode 100644 src/whop_sdk/types/promo_code.py create mode 100644 src/whop_sdk/types/promo_code_create_params.py create mode 100644 src/whop_sdk/types/promo_code_delete_response.py create mode 100644 src/whop_sdk/types/promo_code_list_params.py create mode 100644 src/whop_sdk/types/promo_code_list_response.py create mode 100644 src/whop_sdk/types/promo_code_status.py create mode 100644 src/whop_sdk/types/promo_duration.py create mode 100644 src/whop_sdk/types/shared_params/promo_type.py create mode 100644 tests/api_resources/test_course_chapters.py create mode 100644 tests/api_resources/test_course_lessons.py create mode 100644 tests/api_resources/test_courses.py create mode 100644 tests/api_resources/test_promo_codes.py diff --git a/.stats.yml b/.stats.yml index 865f8237..71124ad0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 81 +configured_endpoints: 103 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-cc2c9659646bfa86078ad117aecdb4ad84f1dd6568cf30feb1fcbea0d99794e0.yml openapi_spec_hash: f1c1d935bb1ab8adf977ca106da721ef -config_hash: 131e5271eed10cf11ebc421d5a0c5f8a +config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/api.md b/api.md index 4195aa21..ffa40e63 100644 --- a/api.md +++ b/api.md @@ -191,6 +191,7 @@ Methods: - client.forum_posts.create(\*\*params) -> ForumPost - client.forum_posts.retrieve(id) -> ForumPost +- client.forum_posts.update(id, \*\*params) -> ForumPost - client.forum_posts.list(\*\*params) -> SyncCursorPage[ForumPostListResponse] # Transfers @@ -304,6 +305,7 @@ Methods: - client.messages.create(\*\*params) -> Message - client.messages.retrieve(id) -> Message +- client.messages.update(id, \*\*params) -> Message - client.messages.list(\*\*params) -> SyncCursorPage[MessageListResponse] # ChatChannels @@ -380,6 +382,7 @@ Methods: - client.experiences.delete(id) -> ExperienceDeleteResponse - client.experiences.attach(id, \*\*params) -> Experience - client.experiences.detach(id, \*\*params) -> Experience +- client.experiences.duplicate(id, \*\*params) -> Experience # Reactions @@ -421,3 +424,79 @@ Methods: - client.forums.retrieve(id) -> Forum - client.forums.update(id, \*\*params) -> Forum - client.forums.list(\*\*params) -> SyncCursorPage[ForumListResponse] + +# PromoCodes + +Types: + +```python +from whop_sdk.types import ( + PromoCode, + PromoCodeStatus, + PromoDuration, + PromoCodeListResponse, + PromoCodeDeleteResponse, +) +``` + +Methods: + +- client.promo_codes.create(\*\*params) -> PromoCode +- client.promo_codes.retrieve(id) -> PromoCode +- client.promo_codes.list(\*\*params) -> SyncCursorPage[PromoCodeListResponse] +- client.promo_codes.delete(id) -> PromoCodeDeleteResponse + +# Courses + +Types: + +```python +from whop_sdk.types import Course, Languages, CourseListResponse, CourseDeleteResponse +``` + +Methods: + +- client.courses.create(\*\*params) -> Course +- client.courses.retrieve(id) -> Course +- client.courses.update(id, \*\*params) -> Course +- client.courses.list(\*\*params) -> SyncCursorPage[CourseListResponse] +- client.courses.delete(id) -> CourseDeleteResponse + +# CourseChapters + +Types: + +```python +from whop_sdk.types import CourseChapter, CourseChapterListResponse, CourseChapterDeleteResponse +``` + +Methods: + +- client.course_chapters.create(\*\*params) -> CourseChapter +- client.course_chapters.retrieve(id) -> CourseChapter +- client.course_chapters.update(id, \*\*params) -> CourseChapter +- client.course_chapters.list(\*\*params) -> SyncCursorPage[CourseChapterListResponse] +- client.course_chapters.delete(id) -> CourseChapterDeleteResponse + +# CourseLessons + +Types: + +```python +from whop_sdk.types import ( + AssessmentQuestionTypes, + Lesson, + LessonTypes, + LessonVisibilities, + CourseLessonListResponse, + CourseLessonDeleteResponse, +) +``` + +Methods: + +- client.course_lessons.create(\*\*params) -> Lesson +- client.course_lessons.retrieve(id) -> Lesson +- client.course_lessons.update(id, \*\*params) -> Lesson +- client.course_lessons.list(\*\*params) -> SyncCursorPage[CourseLessonListResponse] +- client.course_lessons.delete(id) -> CourseLessonDeleteResponse diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 196ca7fb..a0182152 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -26,6 +26,7 @@ plans, users, forums, + courses, entries, members, invoices, @@ -41,7 +42,10 @@ experiences, forum_posts, memberships, + promo_codes, chat_channels, + course_lessons, + course_chapters, ledger_accounts, authorized_users, support_channels, @@ -85,6 +89,10 @@ class Whop(SyncAPIClient): reactions: reactions.ReactionsResource members: members.MembersResource forums: forums.ForumsResource + promo_codes: promo_codes.PromoCodesResource + courses: courses.CoursesResource + course_chapters: course_chapters.CourseChaptersResource + course_lessons: course_lessons.CourseLessonsResource with_raw_response: WhopWithRawResponse with_streaming_response: WhopWithStreamedResponse @@ -186,6 +194,10 @@ def __init__( self.reactions = reactions.ReactionsResource(self) self.members = members.MembersResource(self) self.forums = forums.ForumsResource(self) + self.promo_codes = promo_codes.PromoCodesResource(self) + self.courses = courses.CoursesResource(self) + self.course_chapters = course_chapters.CourseChaptersResource(self) + self.course_lessons = course_lessons.CourseLessonsResource(self) self.with_raw_response = WhopWithRawResponse(self) self.with_streaming_response = WhopWithStreamedResponse(self) @@ -325,6 +337,10 @@ class AsyncWhop(AsyncAPIClient): reactions: reactions.AsyncReactionsResource members: members.AsyncMembersResource forums: forums.AsyncForumsResource + promo_codes: promo_codes.AsyncPromoCodesResource + courses: courses.AsyncCoursesResource + course_chapters: course_chapters.AsyncCourseChaptersResource + course_lessons: course_lessons.AsyncCourseLessonsResource with_raw_response: AsyncWhopWithRawResponse with_streaming_response: AsyncWhopWithStreamedResponse @@ -426,6 +442,10 @@ def __init__( self.reactions = reactions.AsyncReactionsResource(self) self.members = members.AsyncMembersResource(self) self.forums = forums.AsyncForumsResource(self) + self.promo_codes = promo_codes.AsyncPromoCodesResource(self) + self.courses = courses.AsyncCoursesResource(self) + self.course_chapters = course_chapters.AsyncCourseChaptersResource(self) + self.course_lessons = course_lessons.AsyncCourseLessonsResource(self) self.with_raw_response = AsyncWhopWithRawResponse(self) self.with_streaming_response = AsyncWhopWithStreamedResponse(self) @@ -569,6 +589,10 @@ def __init__(self, client: Whop) -> None: self.reactions = reactions.ReactionsResourceWithRawResponse(client.reactions) self.members = members.MembersResourceWithRawResponse(client.members) self.forums = forums.ForumsResourceWithRawResponse(client.forums) + self.promo_codes = promo_codes.PromoCodesResourceWithRawResponse(client.promo_codes) + self.courses = courses.CoursesResourceWithRawResponse(client.courses) + self.course_chapters = course_chapters.CourseChaptersResourceWithRawResponse(client.course_chapters) + self.course_lessons = course_lessons.CourseLessonsResourceWithRawResponse(client.course_lessons) class AsyncWhopWithRawResponse: @@ -603,6 +627,10 @@ def __init__(self, client: AsyncWhop) -> None: self.reactions = reactions.AsyncReactionsResourceWithRawResponse(client.reactions) self.members = members.AsyncMembersResourceWithRawResponse(client.members) self.forums = forums.AsyncForumsResourceWithRawResponse(client.forums) + self.promo_codes = promo_codes.AsyncPromoCodesResourceWithRawResponse(client.promo_codes) + self.courses = courses.AsyncCoursesResourceWithRawResponse(client.courses) + self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithRawResponse(client.course_chapters) + self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithRawResponse(client.course_lessons) class WhopWithStreamedResponse: @@ -637,6 +665,10 @@ def __init__(self, client: Whop) -> None: self.reactions = reactions.ReactionsResourceWithStreamingResponse(client.reactions) self.members = members.MembersResourceWithStreamingResponse(client.members) self.forums = forums.ForumsResourceWithStreamingResponse(client.forums) + self.promo_codes = promo_codes.PromoCodesResourceWithStreamingResponse(client.promo_codes) + self.courses = courses.CoursesResourceWithStreamingResponse(client.courses) + self.course_chapters = course_chapters.CourseChaptersResourceWithStreamingResponse(client.course_chapters) + self.course_lessons = course_lessons.CourseLessonsResourceWithStreamingResponse(client.course_lessons) class AsyncWhopWithStreamedResponse: @@ -675,6 +707,10 @@ def __init__(self, client: AsyncWhop) -> None: self.reactions = reactions.AsyncReactionsResourceWithStreamingResponse(client.reactions) self.members = members.AsyncMembersResourceWithStreamingResponse(client.members) self.forums = forums.AsyncForumsResourceWithStreamingResponse(client.forums) + self.promo_codes = promo_codes.AsyncPromoCodesResourceWithStreamingResponse(client.promo_codes) + self.courses = courses.AsyncCoursesResourceWithStreamingResponse(client.courses) + self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithStreamingResponse(client.course_chapters) + self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithStreamingResponse(client.course_lessons) Client = Whop diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index f3d55211..9c8fe350 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -32,6 +32,14 @@ ForumsResourceWithStreamingResponse, AsyncForumsResourceWithStreamingResponse, ) +from .courses import ( + CoursesResource, + AsyncCoursesResource, + CoursesResourceWithRawResponse, + AsyncCoursesResourceWithRawResponse, + CoursesResourceWithStreamingResponse, + AsyncCoursesResourceWithStreamingResponse, +) from .entries import ( EntriesResource, AsyncEntriesResource, @@ -145,6 +153,14 @@ MembershipsResourceWithStreamingResponse, AsyncMembershipsResourceWithStreamingResponse, ) +from .promo_codes import ( + PromoCodesResource, + AsyncPromoCodesResource, + PromoCodesResourceWithRawResponse, + AsyncPromoCodesResourceWithRawResponse, + PromoCodesResourceWithStreamingResponse, + AsyncPromoCodesResourceWithStreamingResponse, +) from .chat_channels import ( ChatChannelsResource, AsyncChatChannelsResource, @@ -153,6 +169,22 @@ ChatChannelsResourceWithStreamingResponse, AsyncChatChannelsResourceWithStreamingResponse, ) +from .course_lessons import ( + CourseLessonsResource, + AsyncCourseLessonsResource, + CourseLessonsResourceWithRawResponse, + AsyncCourseLessonsResourceWithRawResponse, + CourseLessonsResourceWithStreamingResponse, + AsyncCourseLessonsResourceWithStreamingResponse, +) +from .course_chapters import ( + CourseChaptersResource, + AsyncCourseChaptersResource, + CourseChaptersResourceWithRawResponse, + AsyncCourseChaptersResourceWithRawResponse, + CourseChaptersResourceWithStreamingResponse, + AsyncCourseChaptersResourceWithStreamingResponse, +) from .ledger_accounts import ( LedgerAccountsResource, AsyncLedgerAccountsResource, @@ -341,4 +373,28 @@ "AsyncForumsResourceWithRawResponse", "ForumsResourceWithStreamingResponse", "AsyncForumsResourceWithStreamingResponse", + "PromoCodesResource", + "AsyncPromoCodesResource", + "PromoCodesResourceWithRawResponse", + "AsyncPromoCodesResourceWithRawResponse", + "PromoCodesResourceWithStreamingResponse", + "AsyncPromoCodesResourceWithStreamingResponse", + "CoursesResource", + "AsyncCoursesResource", + "CoursesResourceWithRawResponse", + "AsyncCoursesResourceWithRawResponse", + "CoursesResourceWithStreamingResponse", + "AsyncCoursesResourceWithStreamingResponse", + "CourseChaptersResource", + "AsyncCourseChaptersResource", + "CourseChaptersResourceWithRawResponse", + "AsyncCourseChaptersResourceWithRawResponse", + "CourseChaptersResourceWithStreamingResponse", + "AsyncCourseChaptersResourceWithStreamingResponse", + "CourseLessonsResource", + "AsyncCourseLessonsResource", + "CourseLessonsResourceWithRawResponse", + "AsyncCourseLessonsResourceWithRawResponse", + "CourseLessonsResourceWithStreamingResponse", + "AsyncCourseLessonsResourceWithStreamingResponse", ] diff --git a/src/whop_sdk/resources/course_chapters.py b/src/whop_sdk/resources/course_chapters.py new file mode 100644 index 00000000..008bf0aa --- /dev/null +++ b/src/whop_sdk/resources/course_chapters.py @@ -0,0 +1,602 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..types import course_chapter_list_params, course_chapter_create_params, course_chapter_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.course_chapter import CourseChapter +from ..types.course_chapter_list_response import CourseChapterListResponse +from ..types.course_chapter_delete_response import CourseChapterDeleteResponse + +__all__ = ["CourseChaptersResource", "AsyncCourseChaptersResource"] + + +class CourseChaptersResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CourseChaptersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return CourseChaptersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CourseChaptersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return CourseChaptersResourceWithStreamingResponse(self) + + def create( + self, + *, + course_id: str, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Creates a new course chapter + + Required permissions: + + - `courses:update` + + Args: + course_id: The ID of the course to create the chapter in + + title: The title of the chapter + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/course_chapters", + body=maybe_transform( + { + "course_id": course_id, + "title": title, + }, + course_chapter_create_params.CourseChapterCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Retrieves a course chapter by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/course_chapters/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + def update( + self, + id: str, + *, + title: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Updates a course chapter + + Required permissions: + + - `courses:update` + + Args: + title: The title of the chapter + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/course_chapters/{id}", + body=maybe_transform({"title": title}, course_chapter_update_params.CourseChapterUpdateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + def list( + self, + *, + course_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[CourseChapterListResponse]: + """ + Lists chapters for a course + + Required permissions: + + - `courses:read` + + Args: + course_id: The ID of the course + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/course_chapters", + page=SyncCursorPage[CourseChapterListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "course_id": course_id, + "after": after, + "before": before, + "first": first, + "last": last, + }, + course_chapter_list_params.CourseChapterListParams, + ), + ), + model=CourseChapterListResponse, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapterDeleteResponse: + """ + Deletes a course chapter + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + f"/course_chapters/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapterDeleteResponse, + ) + + +class AsyncCourseChaptersResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCourseChaptersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncCourseChaptersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCourseChaptersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncCourseChaptersResourceWithStreamingResponse(self) + + async def create( + self, + *, + course_id: str, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Creates a new course chapter + + Required permissions: + + - `courses:update` + + Args: + course_id: The ID of the course to create the chapter in + + title: The title of the chapter + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/course_chapters", + body=await async_maybe_transform( + { + "course_id": course_id, + "title": title, + }, + course_chapter_create_params.CourseChapterCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Retrieves a course chapter by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/course_chapters/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + async def update( + self, + id: str, + *, + title: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapter: + """ + Updates a course chapter + + Required permissions: + + - `courses:update` + + Args: + title: The title of the chapter + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/course_chapters/{id}", + body=await async_maybe_transform({"title": title}, course_chapter_update_params.CourseChapterUpdateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapter, + ) + + def list( + self, + *, + course_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CourseChapterListResponse, AsyncCursorPage[CourseChapterListResponse]]: + """ + Lists chapters for a course + + Required permissions: + + - `courses:read` + + Args: + course_id: The ID of the course + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/course_chapters", + page=AsyncCursorPage[CourseChapterListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "course_id": course_id, + "after": after, + "before": before, + "first": first, + "last": last, + }, + course_chapter_list_params.CourseChapterListParams, + ), + ), + model=CourseChapterListResponse, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseChapterDeleteResponse: + """ + Deletes a course chapter + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + f"/course_chapters/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseChapterDeleteResponse, + ) + + +class CourseChaptersResourceWithRawResponse: + def __init__(self, course_chapters: CourseChaptersResource) -> None: + self._course_chapters = course_chapters + + self.create = to_raw_response_wrapper( + course_chapters.create, + ) + self.retrieve = to_raw_response_wrapper( + course_chapters.retrieve, + ) + self.update = to_raw_response_wrapper( + course_chapters.update, + ) + self.list = to_raw_response_wrapper( + course_chapters.list, + ) + self.delete = to_raw_response_wrapper( + course_chapters.delete, + ) + + +class AsyncCourseChaptersResourceWithRawResponse: + def __init__(self, course_chapters: AsyncCourseChaptersResource) -> None: + self._course_chapters = course_chapters + + self.create = async_to_raw_response_wrapper( + course_chapters.create, + ) + self.retrieve = async_to_raw_response_wrapper( + course_chapters.retrieve, + ) + self.update = async_to_raw_response_wrapper( + course_chapters.update, + ) + self.list = async_to_raw_response_wrapper( + course_chapters.list, + ) + self.delete = async_to_raw_response_wrapper( + course_chapters.delete, + ) + + +class CourseChaptersResourceWithStreamingResponse: + def __init__(self, course_chapters: CourseChaptersResource) -> None: + self._course_chapters = course_chapters + + self.create = to_streamed_response_wrapper( + course_chapters.create, + ) + self.retrieve = to_streamed_response_wrapper( + course_chapters.retrieve, + ) + self.update = to_streamed_response_wrapper( + course_chapters.update, + ) + self.list = to_streamed_response_wrapper( + course_chapters.list, + ) + self.delete = to_streamed_response_wrapper( + course_chapters.delete, + ) + + +class AsyncCourseChaptersResourceWithStreamingResponse: + def __init__(self, course_chapters: AsyncCourseChaptersResource) -> None: + self._course_chapters = course_chapters + + self.create = async_to_streamed_response_wrapper( + course_chapters.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + course_chapters.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + course_chapters.update, + ) + self.list = async_to_streamed_response_wrapper( + course_chapters.list, + ) + self.delete = async_to_streamed_response_wrapper( + course_chapters.delete, + ) diff --git a/src/whop_sdk/resources/course_lessons.py b/src/whop_sdk/resources/course_lessons.py new file mode 100644 index 00000000..2afcc1a6 --- /dev/null +++ b/src/whop_sdk/resources/course_lessons.py @@ -0,0 +1,722 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional + +import httpx + +from ..types import ( + LessonTypes, + LessonVisibilities, + course_lesson_list_params, + course_lesson_create_params, + course_lesson_update_params, +) +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.lesson import Lesson +from ..types.lesson_types import LessonTypes +from ..types.lesson_visibilities import LessonVisibilities +from ..types.course_lesson_list_response import CourseLessonListResponse +from ..types.course_lesson_delete_response import CourseLessonDeleteResponse + +__all__ = ["CourseLessonsResource", "AsyncCourseLessonsResource"] + + +class CourseLessonsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CourseLessonsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return CourseLessonsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CourseLessonsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return CourseLessonsResourceWithStreamingResponse(self) + + def create( + self, + *, + chapter_id: str, + lesson_type: LessonTypes, + content: Optional[str] | Omit = omit, + days_from_course_start_until_unlock: Optional[int] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Creates a new course lesson + + Required permissions: + + - `courses:update` + + Args: + chapter_id: The ID of the chapter to create the lesson in + + lesson_type: The type of the lesson + + content: The content of the lesson + + days_from_course_start_until_unlock: Days from course start until unlock + + title: The title of the lesson + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/course_lessons", + body=maybe_transform( + { + "chapter_id": chapter_id, + "lesson_type": lesson_type, + "content": content, + "days_from_course_start_until_unlock": days_from_course_start_until_unlock, + "title": title, + }, + course_lesson_create_params.CourseLessonCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Retrieves a course lesson by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/course_lessons/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + def update( + self, + id: str, + *, + assessment_questions: Optional[Iterable[course_lesson_update_params.AssessmentQuestion]] | Omit = omit, + attachments: Optional[Iterable[course_lesson_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + days_from_course_start_until_unlock: Optional[int] | Omit = omit, + lesson_type: Optional[LessonTypes] | Omit = omit, + main_pdf: Optional[course_lesson_update_params.MainPdf] | Omit = omit, + mux_asset_id: Optional[str] | Omit = omit, + title: Optional[str] | Omit = omit, + visibility: Optional[LessonVisibilities] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Updates a course lesson + + Required permissions: + + - `courses:update` + + Args: + assessment_questions: Assessment questions for quiz/knowledge check lessons. Replaces all existing + questions. + + attachments: General attachments for the lesson (PDFs, files, etc). Replaces all existing + attachments. + + content: The content of the lesson + + days_from_course_start_until_unlock: Days from course start until unlock + + lesson_type: The available types for a lesson + + main_pdf: The main PDF file for this lesson + + mux_asset_id: The ID of the Mux asset to attach to this lesson for video lessons + + title: The title of the lesson + + visibility: The available visibilities for a lesson. Determines how / whether a lesson is + visible to users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/course_lessons/{id}", + body=maybe_transform( + { + "assessment_questions": assessment_questions, + "attachments": attachments, + "content": content, + "days_from_course_start_until_unlock": days_from_course_start_until_unlock, + "lesson_type": lesson_type, + "main_pdf": main_pdf, + "mux_asset_id": mux_asset_id, + "title": title, + "visibility": visibility, + }, + course_lesson_update_params.CourseLessonUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + chapter_id: Optional[str] | Omit = omit, + course_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[CourseLessonListResponse]: + """ + Lists lessons for a course or chapter + + Required permissions: + + - `courses:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + chapter_id: The ID of the chapter (returns lessons only for this chapter) + + course_id: The ID of the course (returns all lessons across all chapters) + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/course_lessons", + page=SyncCursorPage[CourseLessonListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "chapter_id": chapter_id, + "course_id": course_id, + "first": first, + "last": last, + }, + course_lesson_list_params.CourseLessonListParams, + ), + ), + model=CourseLessonListResponse, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseLessonDeleteResponse: + """ + Deletes a course lesson + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + f"/course_lessons/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseLessonDeleteResponse, + ) + + +class AsyncCourseLessonsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCourseLessonsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncCourseLessonsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCourseLessonsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncCourseLessonsResourceWithStreamingResponse(self) + + async def create( + self, + *, + chapter_id: str, + lesson_type: LessonTypes, + content: Optional[str] | Omit = omit, + days_from_course_start_until_unlock: Optional[int] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Creates a new course lesson + + Required permissions: + + - `courses:update` + + Args: + chapter_id: The ID of the chapter to create the lesson in + + lesson_type: The type of the lesson + + content: The content of the lesson + + days_from_course_start_until_unlock: Days from course start until unlock + + title: The title of the lesson + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/course_lessons", + body=await async_maybe_transform( + { + "chapter_id": chapter_id, + "lesson_type": lesson_type, + "content": content, + "days_from_course_start_until_unlock": days_from_course_start_until_unlock, + "title": title, + }, + course_lesson_create_params.CourseLessonCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Retrieves a course lesson by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/course_lessons/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + async def update( + self, + id: str, + *, + assessment_questions: Optional[Iterable[course_lesson_update_params.AssessmentQuestion]] | Omit = omit, + attachments: Optional[Iterable[course_lesson_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + days_from_course_start_until_unlock: Optional[int] | Omit = omit, + lesson_type: Optional[LessonTypes] | Omit = omit, + main_pdf: Optional[course_lesson_update_params.MainPdf] | Omit = omit, + mux_asset_id: Optional[str] | Omit = omit, + title: Optional[str] | Omit = omit, + visibility: Optional[LessonVisibilities] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Lesson: + """ + Updates a course lesson + + Required permissions: + + - `courses:update` + + Args: + assessment_questions: Assessment questions for quiz/knowledge check lessons. Replaces all existing + questions. + + attachments: General attachments for the lesson (PDFs, files, etc). Replaces all existing + attachments. + + content: The content of the lesson + + days_from_course_start_until_unlock: Days from course start until unlock + + lesson_type: The available types for a lesson + + main_pdf: The main PDF file for this lesson + + mux_asset_id: The ID of the Mux asset to attach to this lesson for video lessons + + title: The title of the lesson + + visibility: The available visibilities for a lesson. Determines how / whether a lesson is + visible to users. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/course_lessons/{id}", + body=await async_maybe_transform( + { + "assessment_questions": assessment_questions, + "attachments": attachments, + "content": content, + "days_from_course_start_until_unlock": days_from_course_start_until_unlock, + "lesson_type": lesson_type, + "main_pdf": main_pdf, + "mux_asset_id": mux_asset_id, + "title": title, + "visibility": visibility, + }, + course_lesson_update_params.CourseLessonUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Lesson, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + chapter_id: Optional[str] | Omit = omit, + course_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CourseLessonListResponse, AsyncCursorPage[CourseLessonListResponse]]: + """ + Lists lessons for a course or chapter + + Required permissions: + + - `courses:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + chapter_id: The ID of the chapter (returns lessons only for this chapter) + + course_id: The ID of the course (returns all lessons across all chapters) + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/course_lessons", + page=AsyncCursorPage[CourseLessonListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "chapter_id": chapter_id, + "course_id": course_id, + "first": first, + "last": last, + }, + course_lesson_list_params.CourseLessonListParams, + ), + ), + model=CourseLessonListResponse, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseLessonDeleteResponse: + """ + Deletes a course lesson + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + f"/course_lessons/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseLessonDeleteResponse, + ) + + +class CourseLessonsResourceWithRawResponse: + def __init__(self, course_lessons: CourseLessonsResource) -> None: + self._course_lessons = course_lessons + + self.create = to_raw_response_wrapper( + course_lessons.create, + ) + self.retrieve = to_raw_response_wrapper( + course_lessons.retrieve, + ) + self.update = to_raw_response_wrapper( + course_lessons.update, + ) + self.list = to_raw_response_wrapper( + course_lessons.list, + ) + self.delete = to_raw_response_wrapper( + course_lessons.delete, + ) + + +class AsyncCourseLessonsResourceWithRawResponse: + def __init__(self, course_lessons: AsyncCourseLessonsResource) -> None: + self._course_lessons = course_lessons + + self.create = async_to_raw_response_wrapper( + course_lessons.create, + ) + self.retrieve = async_to_raw_response_wrapper( + course_lessons.retrieve, + ) + self.update = async_to_raw_response_wrapper( + course_lessons.update, + ) + self.list = async_to_raw_response_wrapper( + course_lessons.list, + ) + self.delete = async_to_raw_response_wrapper( + course_lessons.delete, + ) + + +class CourseLessonsResourceWithStreamingResponse: + def __init__(self, course_lessons: CourseLessonsResource) -> None: + self._course_lessons = course_lessons + + self.create = to_streamed_response_wrapper( + course_lessons.create, + ) + self.retrieve = to_streamed_response_wrapper( + course_lessons.retrieve, + ) + self.update = to_streamed_response_wrapper( + course_lessons.update, + ) + self.list = to_streamed_response_wrapper( + course_lessons.list, + ) + self.delete = to_streamed_response_wrapper( + course_lessons.delete, + ) + + +class AsyncCourseLessonsResourceWithStreamingResponse: + def __init__(self, course_lessons: AsyncCourseLessonsResource) -> None: + self._course_lessons = course_lessons + + self.create = async_to_streamed_response_wrapper( + course_lessons.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + course_lessons.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + course_lessons.update, + ) + self.list = async_to_streamed_response_wrapper( + course_lessons.list, + ) + self.delete = async_to_streamed_response_wrapper( + course_lessons.delete, + ) diff --git a/src/whop_sdk/resources/courses.py b/src/whop_sdk/resources/courses.py new file mode 100644 index 00000000..93de3c21 --- /dev/null +++ b/src/whop_sdk/resources/courses.py @@ -0,0 +1,697 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional + +import httpx + +from ..types import Languages, course_list_params, course_create_params, course_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.course import Course +from ..types.languages import Languages +from ..types.course_list_response import CourseListResponse +from ..types.course_delete_response import CourseDeleteResponse + +__all__ = ["CoursesResource", "AsyncCoursesResource"] + + +class CoursesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CoursesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return CoursesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CoursesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return CoursesResourceWithStreamingResponse(self) + + def create( + self, + *, + experience_id: str, + title: str, + tagline: Optional[str] | Omit = omit, + thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Creates a new course module in an experience + + Required permissions: + + - `courses:update` + + Args: + experience_id: The ID of the experience to create the course in + + title: The title of the course + + tagline: The tagline of the course + + thumbnail: The thumbnail for the course in png, jpeg, or gif format + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/courses", + body=maybe_transform( + { + "experience_id": experience_id, + "title": title, + "tagline": tagline, + "thumbnail": thumbnail, + }, + course_create_params.CourseCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Retrieves a course by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/courses/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + def update( + self, + id: str, + *, + certificate_after_completion_enabled: Optional[bool] | Omit = omit, + chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit, + description: Optional[str] | Omit = omit, + language: Optional[Languages] | Omit = omit, + require_completing_lessons_in_order: Optional[bool] | Omit = omit, + tagline: Optional[str] | Omit = omit, + thumbnail: Optional[course_update_params.Thumbnail] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Updates a course + + Required permissions: + + - `courses:update` + + Args: + certificate_after_completion_enabled: Whether the course will award its students a PDF certificate after completing + all lessons + + chapters: The chapters and lessons to update + + description: A short description of the course + + language: The available languages for a course + + require_completing_lessons_in_order: Whether the course requires students to complete the previous lesson before + moving on to the next one + + tagline: A short tagline for the course + + thumbnail: The thumbnail for the course in png, jpeg, or gif format + + title: The title of the course + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/courses/{id}", + body=maybe_transform( + { + "certificate_after_completion_enabled": certificate_after_completion_enabled, + "chapters": chapters, + "description": description, + "language": language, + "require_completing_lessons_in_order": require_completing_lessons_in_order, + "tagline": tagline, + "thumbnail": thumbnail, + "title": title, + }, + course_update_params.CourseUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[CourseListResponse]: + """ + Lists courses for an experience or company + + Required permissions: + + - `courses:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + company_id: The ID of the company + + experience_id: The ID of the experience + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/courses", + page=SyncCursorPage[CourseListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "company_id": company_id, + "experience_id": experience_id, + "first": first, + "last": last, + }, + course_list_params.CourseListParams, + ), + ), + model=CourseListResponse, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseDeleteResponse: + """ + Deletes a course + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + f"/courses/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseDeleteResponse, + ) + + +class AsyncCoursesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCoursesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncCoursesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCoursesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncCoursesResourceWithStreamingResponse(self) + + async def create( + self, + *, + experience_id: str, + title: str, + tagline: Optional[str] | Omit = omit, + thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Creates a new course module in an experience + + Required permissions: + + - `courses:update` + + Args: + experience_id: The ID of the experience to create the course in + + title: The title of the course + + tagline: The tagline of the course + + thumbnail: The thumbnail for the course in png, jpeg, or gif format + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/courses", + body=await async_maybe_transform( + { + "experience_id": experience_id, + "title": title, + "tagline": tagline, + "thumbnail": thumbnail, + }, + course_create_params.CourseCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Retrieves a course by ID + + Required permissions: + + - `courses:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/courses/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + async def update( + self, + id: str, + *, + certificate_after_completion_enabled: Optional[bool] | Omit = omit, + chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit, + description: Optional[str] | Omit = omit, + language: Optional[Languages] | Omit = omit, + require_completing_lessons_in_order: Optional[bool] | Omit = omit, + tagline: Optional[str] | Omit = omit, + thumbnail: Optional[course_update_params.Thumbnail] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Course: + """ + Updates a course + + Required permissions: + + - `courses:update` + + Args: + certificate_after_completion_enabled: Whether the course will award its students a PDF certificate after completing + all lessons + + chapters: The chapters and lessons to update + + description: A short description of the course + + language: The available languages for a course + + require_completing_lessons_in_order: Whether the course requires students to complete the previous lesson before + moving on to the next one + + tagline: A short tagline for the course + + thumbnail: The thumbnail for the course in png, jpeg, or gif format + + title: The title of the course + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/courses/{id}", + body=await async_maybe_transform( + { + "certificate_after_completion_enabled": certificate_after_completion_enabled, + "chapters": chapters, + "description": description, + "language": language, + "require_completing_lessons_in_order": require_completing_lessons_in_order, + "tagline": tagline, + "thumbnail": thumbnail, + "title": title, + }, + course_update_params.CourseUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Course, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CourseListResponse, AsyncCursorPage[CourseListResponse]]: + """ + Lists courses for an experience or company + + Required permissions: + + - `courses:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + company_id: The ID of the company + + experience_id: The ID of the experience + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/courses", + page=AsyncCursorPage[CourseListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "company_id": company_id, + "experience_id": experience_id, + "first": first, + "last": last, + }, + course_list_params.CourseListParams, + ), + ), + model=CourseListResponse, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CourseDeleteResponse: + """ + Deletes a course + + Required permissions: + + - `courses:update` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + f"/courses/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CourseDeleteResponse, + ) + + +class CoursesResourceWithRawResponse: + def __init__(self, courses: CoursesResource) -> None: + self._courses = courses + + self.create = to_raw_response_wrapper( + courses.create, + ) + self.retrieve = to_raw_response_wrapper( + courses.retrieve, + ) + self.update = to_raw_response_wrapper( + courses.update, + ) + self.list = to_raw_response_wrapper( + courses.list, + ) + self.delete = to_raw_response_wrapper( + courses.delete, + ) + + +class AsyncCoursesResourceWithRawResponse: + def __init__(self, courses: AsyncCoursesResource) -> None: + self._courses = courses + + self.create = async_to_raw_response_wrapper( + courses.create, + ) + self.retrieve = async_to_raw_response_wrapper( + courses.retrieve, + ) + self.update = async_to_raw_response_wrapper( + courses.update, + ) + self.list = async_to_raw_response_wrapper( + courses.list, + ) + self.delete = async_to_raw_response_wrapper( + courses.delete, + ) + + +class CoursesResourceWithStreamingResponse: + def __init__(self, courses: CoursesResource) -> None: + self._courses = courses + + self.create = to_streamed_response_wrapper( + courses.create, + ) + self.retrieve = to_streamed_response_wrapper( + courses.retrieve, + ) + self.update = to_streamed_response_wrapper( + courses.update, + ) + self.list = to_streamed_response_wrapper( + courses.list, + ) + self.delete = to_streamed_response_wrapper( + courses.delete, + ) + + +class AsyncCoursesResourceWithStreamingResponse: + def __init__(self, courses: AsyncCoursesResource) -> None: + self._courses = courses + + self.create = async_to_streamed_response_wrapper( + courses.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + courses.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + courses.update, + ) + self.list = async_to_streamed_response_wrapper( + courses.list, + ) + self.delete = async_to_streamed_response_wrapper( + courses.delete, + ) diff --git a/src/whop_sdk/resources/experiences.py b/src/whop_sdk/resources/experiences.py index 41e06c65..7a7bca4f 100644 --- a/src/whop_sdk/resources/experiences.py +++ b/src/whop_sdk/resources/experiences.py @@ -13,6 +13,7 @@ experience_create_params, experience_detach_params, experience_update_params, + experience_duplicate_params, ) from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform @@ -389,6 +390,45 @@ def detach( cast_to=Experience, ) + def duplicate( + self, + id: str, + *, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Experience: + """ + Required permissions: + + - `experience:create` + + Args: + name: The name of the new experience + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + f"/experiences/{id}/duplicate", + body=maybe_transform({"name": name}, experience_duplicate_params.ExperienceDuplicateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Experience, + ) + class AsyncExperiencesResource(AsyncAPIResource): @cached_property @@ -750,6 +790,45 @@ async def detach( cast_to=Experience, ) + async def duplicate( + self, + id: str, + *, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Experience: + """ + Required permissions: + + - `experience:create` + + Args: + name: The name of the new experience + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + f"/experiences/{id}/duplicate", + body=await async_maybe_transform({"name": name}, experience_duplicate_params.ExperienceDuplicateParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Experience, + ) + class ExperiencesResourceWithRawResponse: def __init__(self, experiences: ExperiencesResource) -> None: @@ -776,6 +855,9 @@ def __init__(self, experiences: ExperiencesResource) -> None: self.detach = to_raw_response_wrapper( experiences.detach, ) + self.duplicate = to_raw_response_wrapper( + experiences.duplicate, + ) class AsyncExperiencesResourceWithRawResponse: @@ -803,6 +885,9 @@ def __init__(self, experiences: AsyncExperiencesResource) -> None: self.detach = async_to_raw_response_wrapper( experiences.detach, ) + self.duplicate = async_to_raw_response_wrapper( + experiences.duplicate, + ) class ExperiencesResourceWithStreamingResponse: @@ -830,6 +915,9 @@ def __init__(self, experiences: ExperiencesResource) -> None: self.detach = to_streamed_response_wrapper( experiences.detach, ) + self.duplicate = to_streamed_response_wrapper( + experiences.duplicate, + ) class AsyncExperiencesResourceWithStreamingResponse: @@ -857,3 +945,6 @@ def __init__(self, experiences: AsyncExperiencesResource) -> None: self.detach = async_to_streamed_response_wrapper( experiences.detach, ) + self.duplicate = async_to_streamed_response_wrapper( + experiences.duplicate, + ) diff --git a/src/whop_sdk/resources/forum_posts.py b/src/whop_sdk/resources/forum_posts.py index 8b744103..87ab5118 100644 --- a/src/whop_sdk/resources/forum_posts.py +++ b/src/whop_sdk/resources/forum_posts.py @@ -6,7 +6,7 @@ import httpx -from ..types import forum_post_list_params, forum_post_create_params +from ..types import forum_post_list_params, forum_post_create_params, forum_post_update_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -167,6 +167,61 @@ def retrieve( cast_to=ForumPost, ) + def update( + self, + id: str, + *, + attachments: Optional[Iterable[forum_post_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + is_pinned: Optional[bool] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ForumPost: + """ + Update an existing forum post + + Args: + attachments: The attachments for this post + + content: This is the main body of the post in Markdown format. Hidden if paywalled and + user hasn't purchased access to it. + + is_pinned: Whether the post is pinned. You can only pin a top level posts (not comments). + + title: The title of the post. Only visible if paywalled. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/forum_posts/{id}", + body=maybe_transform( + { + "attachments": attachments, + "content": content, + "is_pinned": is_pinned, + "title": title, + }, + forum_post_update_params.ForumPostUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ForumPost, + ) + def list( self, *, @@ -380,6 +435,61 @@ async def retrieve( cast_to=ForumPost, ) + async def update( + self, + id: str, + *, + attachments: Optional[Iterable[forum_post_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + is_pinned: Optional[bool] | Omit = omit, + title: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ForumPost: + """ + Update an existing forum post + + Args: + attachments: The attachments for this post + + content: This is the main body of the post in Markdown format. Hidden if paywalled and + user hasn't purchased access to it. + + is_pinned: Whether the post is pinned. You can only pin a top level posts (not comments). + + title: The title of the post. Only visible if paywalled. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/forum_posts/{id}", + body=await async_maybe_transform( + { + "attachments": attachments, + "content": content, + "is_pinned": is_pinned, + "title": title, + }, + forum_post_update_params.ForumPostUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ForumPost, + ) + def list( self, *, @@ -462,6 +572,9 @@ def __init__(self, forum_posts: ForumPostsResource) -> None: self.retrieve = to_raw_response_wrapper( forum_posts.retrieve, ) + self.update = to_raw_response_wrapper( + forum_posts.update, + ) self.list = to_raw_response_wrapper( forum_posts.list, ) @@ -477,6 +590,9 @@ def __init__(self, forum_posts: AsyncForumPostsResource) -> None: self.retrieve = async_to_raw_response_wrapper( forum_posts.retrieve, ) + self.update = async_to_raw_response_wrapper( + forum_posts.update, + ) self.list = async_to_raw_response_wrapper( forum_posts.list, ) @@ -492,6 +608,9 @@ def __init__(self, forum_posts: ForumPostsResource) -> None: self.retrieve = to_streamed_response_wrapper( forum_posts.retrieve, ) + self.update = to_streamed_response_wrapper( + forum_posts.update, + ) self.list = to_streamed_response_wrapper( forum_posts.list, ) @@ -507,6 +626,9 @@ def __init__(self, forum_posts: AsyncForumPostsResource) -> None: self.retrieve = async_to_streamed_response_wrapper( forum_posts.retrieve, ) + self.update = async_to_streamed_response_wrapper( + forum_posts.update, + ) self.list = async_to_streamed_response_wrapper( forum_posts.list, ) diff --git a/src/whop_sdk/resources/messages.py b/src/whop_sdk/resources/messages.py index a5f5aed5..589bfc7b 100644 --- a/src/whop_sdk/resources/messages.py +++ b/src/whop_sdk/resources/messages.py @@ -6,7 +6,7 @@ import httpx -from ..types import message_list_params, message_create_params +from ..types import message_list_params, message_create_params, message_update_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -138,6 +138,56 @@ def retrieve( cast_to=Message, ) + def update( + self, + id: str, + *, + attachments: Optional[Iterable[message_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + is_pinned: Optional[bool] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Message: + """ + Updates an existing message + + Args: + attachments: The attachments for this message + + content: The content of the message in Markdown format + + is_pinned: Whether this message is pinned + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/messages/{id}", + body=maybe_transform( + { + "attachments": attachments, + "content": content, + "is_pinned": is_pinned, + }, + message_update_params.MessageUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Message, + ) + def list( self, *, @@ -318,6 +368,56 @@ async def retrieve( cast_to=Message, ) + async def update( + self, + id: str, + *, + attachments: Optional[Iterable[message_update_params.Attachment]] | Omit = omit, + content: Optional[str] | Omit = omit, + is_pinned: Optional[bool] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Message: + """ + Updates an existing message + + Args: + attachments: The attachments for this message + + content: The content of the message in Markdown format + + is_pinned: Whether this message is pinned + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/messages/{id}", + body=await async_maybe_transform( + { + "attachments": attachments, + "content": content, + "is_pinned": is_pinned, + }, + message_update_params.MessageUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Message, + ) + def list( self, *, @@ -396,6 +496,9 @@ def __init__(self, messages: MessagesResource) -> None: self.retrieve = to_raw_response_wrapper( messages.retrieve, ) + self.update = to_raw_response_wrapper( + messages.update, + ) self.list = to_raw_response_wrapper( messages.list, ) @@ -411,6 +514,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.retrieve = async_to_raw_response_wrapper( messages.retrieve, ) + self.update = async_to_raw_response_wrapper( + messages.update, + ) self.list = async_to_raw_response_wrapper( messages.list, ) @@ -426,6 +532,9 @@ def __init__(self, messages: MessagesResource) -> None: self.retrieve = to_streamed_response_wrapper( messages.retrieve, ) + self.update = to_streamed_response_wrapper( + messages.update, + ) self.list = to_streamed_response_wrapper( messages.list, ) @@ -441,6 +550,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.retrieve = async_to_streamed_response_wrapper( messages.retrieve, ) + self.update = async_to_streamed_response_wrapper( + messages.update, + ) self.list = async_to_streamed_response_wrapper( messages.list, ) diff --git a/src/whop_sdk/resources/promo_codes.py b/src/whop_sdk/resources/promo_codes.py new file mode 100644 index 00000000..ad73e9d5 --- /dev/null +++ b/src/whop_sdk/resources/promo_codes.py @@ -0,0 +1,652 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime + +import httpx + +from ..types import PromoCodeStatus, promo_code_list_params, promo_code_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.promo_code import PromoCode +from ..types.shared.currency import Currency +from ..types.promo_code_status import PromoCodeStatus +from ..types.shared.promo_type import PromoType +from ..types.promo_code_list_response import PromoCodeListResponse +from ..types.promo_code_delete_response import PromoCodeDeleteResponse + +__all__ = ["PromoCodesResource", "AsyncPromoCodesResource"] + + +class PromoCodesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> PromoCodesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return PromoCodesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PromoCodesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return PromoCodesResourceWithStreamingResponse(self) + + def create( + self, + *, + amount_off: float, + base_currency: Currency, + code: str, + company_id: str, + new_users_only: bool, + promo_duration_months: int, + promo_type: PromoType, + churned_users_only: Optional[bool] | Omit = omit, + existing_memberships_only: Optional[bool] | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, + one_per_customer: Optional[bool] | Omit = omit, + plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, + product_id: Optional[str] | Omit = omit, + stock: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCode: + """ + Create a new promo code for a product or plan + + Required permissions: + + - `promo_code:create` + - `access_pass:basic:read` + + Args: + amount_off: The amount off (% or flat amount) for the promo. + + base_currency: The monetary currency of the promo code. + + code: The specific code used to apply the promo at checkout. + + company_id: The id of the company to create the promo code for. + + new_users_only: Restricts promo use to only users who have never purchased from the company + before. + + promo_duration_months: The number of months this promo code is applied and valid for. + + promo_type: The type (% or flat amount) of the promo. + + churned_users_only: Restricts promo use to only users who have churned from the company before. + + existing_memberships_only: Whether this promo code is for existing memberships only (cancelations) + + expires_at: The date/time of when the promo expires. + + one_per_customer: Restricts promo use to only be applied once per customer. + + plan_ids: The IDs of the plans that the promo code applies to. If product_id is provided, + it will only apply to plans attached to that product + + product_id: The product to lock the promo code to, if any. If provided will filter out any + plan ids not attached to this product + + stock: The quantity limit on the number of uses. + + unlimited_stock: Whether or not the promo code should have unlimited stock. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/promo_codes", + body=maybe_transform( + { + "amount_off": amount_off, + "base_currency": base_currency, + "code": code, + "company_id": company_id, + "new_users_only": new_users_only, + "promo_duration_months": promo_duration_months, + "promo_type": promo_type, + "churned_users_only": churned_users_only, + "existing_memberships_only": existing_memberships_only, + "expires_at": expires_at, + "one_per_customer": one_per_customer, + "plan_ids": plan_ids, + "product_id": product_id, + "stock": stock, + "unlimited_stock": unlimited_stock, + }, + promo_code_create_params.PromoCodeCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCode, + ) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCode: + """ + Retrieves a promo code by ID + + Required permissions: + + - `promo_code:basic:read` + - `access_pass:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/promo_codes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCode, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, + product_ids: Optional[SequenceNotStr[str]] | Omit = omit, + status: Optional[PromoCodeStatus] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[PromoCodeListResponse]: + """ + Lists promo codes for a company + + Required permissions: + + - `promo_code:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The ID of the company to list promo codes for + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + plan_ids: Filter promo codes by plan ID(s) + + product_ids: Filter promo codes by product ID(s) + + status: Statuses for promo codes + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/promo_codes", + page=SyncCursorPage[PromoCodeListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "first": first, + "last": last, + "plan_ids": plan_ids, + "product_ids": product_ids, + "status": status, + }, + promo_code_list_params.PromoCodeListParams, + ), + ), + model=PromoCodeListResponse, + ) + + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCodeDeleteResponse: + """ + Archive a promo code, preventing further use + + Required permissions: + + - `promo_code:delete` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + f"/promo_codes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCodeDeleteResponse, + ) + + +class AsyncPromoCodesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncPromoCodesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncPromoCodesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPromoCodesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncPromoCodesResourceWithStreamingResponse(self) + + async def create( + self, + *, + amount_off: float, + base_currency: Currency, + code: str, + company_id: str, + new_users_only: bool, + promo_duration_months: int, + promo_type: PromoType, + churned_users_only: Optional[bool] | Omit = omit, + existing_memberships_only: Optional[bool] | Omit = omit, + expires_at: Union[str, datetime, None] | Omit = omit, + one_per_customer: Optional[bool] | Omit = omit, + plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, + product_id: Optional[str] | Omit = omit, + stock: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCode: + """ + Create a new promo code for a product or plan + + Required permissions: + + - `promo_code:create` + - `access_pass:basic:read` + + Args: + amount_off: The amount off (% or flat amount) for the promo. + + base_currency: The monetary currency of the promo code. + + code: The specific code used to apply the promo at checkout. + + company_id: The id of the company to create the promo code for. + + new_users_only: Restricts promo use to only users who have never purchased from the company + before. + + promo_duration_months: The number of months this promo code is applied and valid for. + + promo_type: The type (% or flat amount) of the promo. + + churned_users_only: Restricts promo use to only users who have churned from the company before. + + existing_memberships_only: Whether this promo code is for existing memberships only (cancelations) + + expires_at: The date/time of when the promo expires. + + one_per_customer: Restricts promo use to only be applied once per customer. + + plan_ids: The IDs of the plans that the promo code applies to. If product_id is provided, + it will only apply to plans attached to that product + + product_id: The product to lock the promo code to, if any. If provided will filter out any + plan ids not attached to this product + + stock: The quantity limit on the number of uses. + + unlimited_stock: Whether or not the promo code should have unlimited stock. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/promo_codes", + body=await async_maybe_transform( + { + "amount_off": amount_off, + "base_currency": base_currency, + "code": code, + "company_id": company_id, + "new_users_only": new_users_only, + "promo_duration_months": promo_duration_months, + "promo_type": promo_type, + "churned_users_only": churned_users_only, + "existing_memberships_only": existing_memberships_only, + "expires_at": expires_at, + "one_per_customer": one_per_customer, + "plan_ids": plan_ids, + "product_id": product_id, + "stock": stock, + "unlimited_stock": unlimited_stock, + }, + promo_code_create_params.PromoCodeCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCode, + ) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCode: + """ + Retrieves a promo code by ID + + Required permissions: + + - `promo_code:basic:read` + - `access_pass:basic:read` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/promo_codes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCode, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, + product_ids: Optional[SequenceNotStr[str]] | Omit = omit, + status: Optional[PromoCodeStatus] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[PromoCodeListResponse, AsyncCursorPage[PromoCodeListResponse]]: + """ + Lists promo codes for a company + + Required permissions: + + - `promo_code:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The ID of the company to list promo codes for + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + plan_ids: Filter promo codes by plan ID(s) + + product_ids: Filter promo codes by product ID(s) + + status: Statuses for promo codes + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/promo_codes", + page=AsyncCursorPage[PromoCodeListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "first": first, + "last": last, + "plan_ids": plan_ids, + "product_ids": product_ids, + "status": status, + }, + promo_code_list_params.PromoCodeListParams, + ), + ), + model=PromoCodeListResponse, + ) + + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PromoCodeDeleteResponse: + """ + Archive a promo code, preventing further use + + Required permissions: + + - `promo_code:delete` + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + f"/promo_codes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PromoCodeDeleteResponse, + ) + + +class PromoCodesResourceWithRawResponse: + def __init__(self, promo_codes: PromoCodesResource) -> None: + self._promo_codes = promo_codes + + self.create = to_raw_response_wrapper( + promo_codes.create, + ) + self.retrieve = to_raw_response_wrapper( + promo_codes.retrieve, + ) + self.list = to_raw_response_wrapper( + promo_codes.list, + ) + self.delete = to_raw_response_wrapper( + promo_codes.delete, + ) + + +class AsyncPromoCodesResourceWithRawResponse: + def __init__(self, promo_codes: AsyncPromoCodesResource) -> None: + self._promo_codes = promo_codes + + self.create = async_to_raw_response_wrapper( + promo_codes.create, + ) + self.retrieve = async_to_raw_response_wrapper( + promo_codes.retrieve, + ) + self.list = async_to_raw_response_wrapper( + promo_codes.list, + ) + self.delete = async_to_raw_response_wrapper( + promo_codes.delete, + ) + + +class PromoCodesResourceWithStreamingResponse: + def __init__(self, promo_codes: PromoCodesResource) -> None: + self._promo_codes = promo_codes + + self.create = to_streamed_response_wrapper( + promo_codes.create, + ) + self.retrieve = to_streamed_response_wrapper( + promo_codes.retrieve, + ) + self.list = to_streamed_response_wrapper( + promo_codes.list, + ) + self.delete = to_streamed_response_wrapper( + promo_codes.delete, + ) + + +class AsyncPromoCodesResourceWithStreamingResponse: + def __init__(self, promo_codes: AsyncPromoCodesResource) -> None: + self._promo_codes = promo_codes + + self.create = async_to_streamed_response_wrapper( + promo_codes.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + promo_codes.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + promo_codes.list, + ) + self.delete = async_to_streamed_response_wrapper( + promo_codes.delete, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 37c1e57a..22d1d3bf 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -2,6 +2,8 @@ from __future__ import annotations +from .course import Course as Course +from .lesson import Lesson as Lesson from .shared import ( App as App, Plan as Plan, @@ -64,6 +66,11 @@ EmailNotificationPreferences as EmailNotificationPreferences, CourseLessonInteractionListItem as CourseLessonInteractionListItem, ) +from .languages import Languages as Languages +from .promo_code import PromoCode as PromoCode +from .lesson_types import LessonTypes as LessonTypes +from .course_chapter import CourseChapter as CourseChapter +from .promo_duration import PromoDuration as PromoDuration from .app_list_params import AppListParams as AppListParams from .plan_list_params import PlanListParams as PlanListParams from .app_create_params import AppCreateParams as AppCreateParams @@ -71,6 +78,8 @@ from .app_update_params import AppUpdateParams as AppUpdateParams from .entry_list_params import EntryListParams as EntryListParams from .forum_list_params import ForumListParams as ForumListParams +from .promo_code_status import PromoCodeStatus as PromoCodeStatus +from .course_list_params import CourseListParams as CourseListParams from .member_list_params import MemberListParams as MemberListParams from .plan_create_params import PlanCreateParams as PlanCreateParams from .plan_list_response import PlanListResponse as PlanListResponse @@ -79,9 +88,13 @@ from .forum_list_response import ForumListResponse as ForumListResponse from .forum_update_params import ForumUpdateParams as ForumUpdateParams from .invoice_list_params import InvoiceListParams as InvoiceListParams +from .lesson_visibilities import LessonVisibilities as LessonVisibilities from .message_list_params import MessageListParams as MessageListParams from .payment_list_params import PaymentListParams as PaymentListParams from .product_list_params import ProductListParams as ProductListParams +from .course_create_params import CourseCreateParams as CourseCreateParams +from .course_list_response import CourseListResponse as CourseListResponse +from .course_update_params import CourseUpdateParams as CourseUpdateParams from .member_list_response import MemberListResponse as MemberListResponse from .plan_delete_response import PlanDeleteResponse as PlanDeleteResponse from .reaction_list_params import ReactionListParams as ReactionListParams @@ -93,14 +106,17 @@ from .invoice_void_response import InvoiceVoidResponse as InvoiceVoidResponse from .message_create_params import MessageCreateParams as MessageCreateParams from .message_list_response import MessageListResponse as MessageListResponse +from .message_update_params import MessageUpdateParams as MessageUpdateParams from .payment_list_response import PaymentListResponse as PaymentListResponse from .payment_refund_params import PaymentRefundParams as PaymentRefundParams from .product_create_params import ProductCreateParams as ProductCreateParams from .product_update_params import ProductUpdateParams as ProductUpdateParams +from .course_delete_response import CourseDeleteResponse as CourseDeleteResponse from .entry_approve_response import EntryApproveResponse as EntryApproveResponse from .experience_list_params import ExperienceListParams as ExperienceListParams from .forum_post_list_params import ForumPostListParams as ForumPostListParams from .membership_list_params import MembershipListParams as MembershipListParams +from .promo_code_list_params import PromoCodeListParams as PromoCodeListParams from .reaction_create_params import ReactionCreateParams as ReactionCreateParams from .reaction_list_response import ReactionListResponse as ReactionListResponse from .shipment_create_params import ShipmentCreateParams as ShipmentCreateParams @@ -121,28 +137,44 @@ from .experience_update_params import ExperienceUpdateParams as ExperienceUpdateParams from .forum_post_create_params import ForumPostCreateParams as ForumPostCreateParams from .forum_post_list_response import ForumPostListResponse as ForumPostListResponse +from .forum_post_update_params import ForumPostUpdateParams as ForumPostUpdateParams from .member_retrieve_response import MemberRetrieveResponse as MemberRetrieveResponse from .membership_cancel_params import MembershipCancelParams as MembershipCancelParams from .membership_list_response import MembershipListResponse as MembershipListResponse from .membership_update_params import MembershipUpdateParams as MembershipUpdateParams +from .promo_code_create_params import PromoCodeCreateParams as PromoCodeCreateParams +from .promo_code_list_response import PromoCodeListResponse as PromoCodeListResponse +from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes +from .course_lesson_list_params import CourseLessonListParams as CourseLessonListParams from .chat_channel_list_response import ChatChannelListResponse as ChatChannelListResponse from .chat_channel_update_params import ChatChannelUpdateParams as ChatChannelUpdateParams +from .course_chapter_list_params import CourseChapterListParams as CourseChapterListParams from .entry_denied_webhook_event import EntryDeniedWebhookEvent as EntryDeniedWebhookEvent from .experience_delete_response import ExperienceDeleteResponse as ExperienceDeleteResponse from .invoice_paid_webhook_event import InvoicePaidWebhookEvent as InvoicePaidWebhookEvent +from .promo_code_delete_response import PromoCodeDeleteResponse as PromoCodeDeleteResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams +from .course_lesson_create_params import CourseLessonCreateParams as CourseLessonCreateParams +from .course_lesson_list_response import CourseLessonListResponse as CourseLessonListResponse +from .course_lesson_update_params import CourseLessonUpdateParams as CourseLessonUpdateParams from .entry_created_webhook_event import EntryCreatedWebhookEvent as EntryCreatedWebhookEvent from .entry_deleted_webhook_event import EntryDeletedWebhookEvent as EntryDeletedWebhookEvent +from .experience_duplicate_params import ExperienceDuplicateParams as ExperienceDuplicateParams from .support_channel_list_params import SupportChannelListParams as SupportChannelListParams +from .course_chapter_create_params import CourseChapterCreateParams as CourseChapterCreateParams +from .course_chapter_list_response import CourseChapterListResponse as CourseChapterListResponse +from .course_chapter_update_params import CourseChapterUpdateParams as CourseChapterUpdateParams from .entry_approved_webhook_event import EntryApprovedWebhookEvent as EntryApprovedWebhookEvent from .invoice_voided_webhook_event import InvoiceVoidedWebhookEvent as InvoiceVoidedWebhookEvent from .payment_failed_webhook_event import PaymentFailedWebhookEvent as PaymentFailedWebhookEvent from .authorized_user_list_response import AuthorizedUserListResponse as AuthorizedUserListResponse +from .course_lesson_delete_response import CourseLessonDeleteResponse as CourseLessonDeleteResponse from .invoice_created_webhook_event import InvoiceCreatedWebhookEvent as InvoiceCreatedWebhookEvent from .payment_pending_webhook_event import PaymentPendingWebhookEvent as PaymentPendingWebhookEvent from .support_channel_create_params import SupportChannelCreateParams as SupportChannelCreateParams from .support_channel_list_response import SupportChannelListResponse as SupportChannelListResponse +from .course_chapter_delete_response import CourseChapterDeleteResponse as CourseChapterDeleteResponse from .invoice_past_due_webhook_event import InvoicePastDueWebhookEvent as InvoicePastDueWebhookEvent from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent as PaymentSucceededWebhookEvent from .ledger_account_retrieve_response import LedgerAccountRetrieveResponse as LedgerAccountRetrieveResponse diff --git a/src/whop_sdk/types/assessment_question_types.py b/src/whop_sdk/types/assessment_question_types.py new file mode 100644 index 00000000..35cf1f44 --- /dev/null +++ b/src/whop_sdk/types/assessment_question_types.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["AssessmentQuestionTypes"] + +AssessmentQuestionTypes: TypeAlias = Literal["short_answer", "true_false", "multiple_choice", "multiple_select"] diff --git a/src/whop_sdk/types/course.py b/src/whop_sdk/types/course.py new file mode 100644 index 00000000..84fd76a9 --- /dev/null +++ b/src/whop_sdk/types/course.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .languages import Languages +from .lesson_types import LessonTypes + +__all__ = ["Course", "Chapter", "ChapterLesson", "Thumbnail"] + + +class ChapterLesson(BaseModel): + id: str + """The ID of the lesson""" + + lesson_type: LessonTypes + """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)""" + + order: int + """The order of the lesson within its chapter""" + + title: str + """The title of the lesson""" + + +class Chapter(BaseModel): + id: str + """The ID of the chapter. Looks like chap_XXX""" + + lessons: List[ChapterLesson] + """The lessons in this chapter""" + + order: int + """The order of the chapter within its course""" + + title: str + """The title of the chapter""" + + +class Thumbnail(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + optimized_url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + source_url: Optional[str] = None + """The original URL of the attachment, such as a direct link to S3. + + This should never be displayed on the client and always passed to an Imgproxy + transformer. + """ + + +class Course(BaseModel): + id: str + """The ID of the course. Looks like cors_XXX""" + + certificate_after_completion_enabled: Optional[bool] = None + """ + Whether the course will award its students a PDF certificate after completing + all lessons + """ + + chapters: List[Chapter] + """The chapters in this course""" + + description: Optional[str] = None + """A short description of the course""" + + language: Languages + """ + The language spoken in the video content of the course, used to generate closed + captions in the right language + """ + + require_completing_lessons_in_order: bool + """ + Whether the course requires students to complete the previous lesson before + moving on to the next one + """ + + tagline: Optional[str] = None + """A short tagline for the course. + + It is displayed under the course title in the UI + """ + + thumbnail: Optional[Thumbnail] = None + """The thumbnail for the course""" + + title: Optional[str] = None + """The title of the course""" diff --git a/src/whop_sdk/types/course_chapter.py b/src/whop_sdk/types/course_chapter.py new file mode 100644 index 00000000..48a6de54 --- /dev/null +++ b/src/whop_sdk/types/course_chapter.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["CourseChapter", "Lesson"] + + +class Lesson(BaseModel): + id: str + """The ID of the lesson""" + + order: int + """The order of the lesson within its chapter""" + + title: str + """The title of the lesson""" + + +class CourseChapter(BaseModel): + id: str + """The ID of the chapter. Looks like chap_XXX""" + + lessons: List[Lesson] + """The lessons in this chapter""" + + order: int + """The order of the chapter within its course""" + + title: str + """The title of the chapter""" diff --git a/src/whop_sdk/types/course_chapter_create_params.py b/src/whop_sdk/types/course_chapter_create_params.py new file mode 100644 index 00000000..1d4d7dfb --- /dev/null +++ b/src/whop_sdk/types/course_chapter_create_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["CourseChapterCreateParams"] + + +class CourseChapterCreateParams(TypedDict, total=False): + course_id: Required[str] + """The ID of the course to create the chapter in""" + + title: Optional[str] + """The title of the chapter""" diff --git a/src/whop_sdk/types/course_chapter_delete_response.py b/src/whop_sdk/types/course_chapter_delete_response.py new file mode 100644 index 00000000..3c6ef823 --- /dev/null +++ b/src/whop_sdk/types/course_chapter_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["CourseChapterDeleteResponse"] + +CourseChapterDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/course_chapter_list_params.py b/src/whop_sdk/types/course_chapter_list_params.py new file mode 100644 index 00000000..211423f3 --- /dev/null +++ b/src/whop_sdk/types/course_chapter_list_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["CourseChapterListParams"] + + +class CourseChapterListParams(TypedDict, total=False): + course_id: Required[str] + """The ID of the course""" + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" diff --git a/src/whop_sdk/types/course_chapter_list_response.py b/src/whop_sdk/types/course_chapter_list_response.py new file mode 100644 index 00000000..e6bb4610 --- /dev/null +++ b/src/whop_sdk/types/course_chapter_list_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["CourseChapterListResponse"] + + +class CourseChapterListResponse(BaseModel): + id: str + """The ID of the chapter. Looks like chap_XXX""" + + order: int + """The order of the chapter within its course""" + + title: str + """The title of the chapter""" diff --git a/src/whop_sdk/types/course_chapter_update_params.py b/src/whop_sdk/types/course_chapter_update_params.py new file mode 100644 index 00000000..706b8f4f --- /dev/null +++ b/src/whop_sdk/types/course_chapter_update_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["CourseChapterUpdateParams"] + + +class CourseChapterUpdateParams(TypedDict, total=False): + title: Required[str] + """The title of the chapter""" diff --git a/src/whop_sdk/types/course_create_params.py b/src/whop_sdk/types/course_create_params.py new file mode 100644 index 00000000..a5cedada --- /dev/null +++ b/src/whop_sdk/types/course_create_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["CourseCreateParams", "Thumbnail"] + + +class CourseCreateParams(TypedDict, total=False): + experience_id: Required[str] + """The ID of the experience to create the course in""" + + title: Required[str] + """The title of the course""" + + tagline: Optional[str] + """The tagline of the course""" + + thumbnail: Optional[Thumbnail] + """The thumbnail for the course in png, jpeg, or gif format""" + + +class Thumbnail(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ diff --git a/src/whop_sdk/types/course_delete_response.py b/src/whop_sdk/types/course_delete_response.py new file mode 100644 index 00000000..72c3dfaf --- /dev/null +++ b/src/whop_sdk/types/course_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["CourseDeleteResponse"] + +CourseDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/course_lesson_create_params.py b/src/whop_sdk/types/course_lesson_create_params.py new file mode 100644 index 00000000..29f6fdd2 --- /dev/null +++ b/src/whop_sdk/types/course_lesson_create_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from .lesson_types import LessonTypes + +__all__ = ["CourseLessonCreateParams"] + + +class CourseLessonCreateParams(TypedDict, total=False): + chapter_id: Required[str] + """The ID of the chapter to create the lesson in""" + + lesson_type: Required[LessonTypes] + """The type of the lesson""" + + content: Optional[str] + """The content of the lesson""" + + days_from_course_start_until_unlock: Optional[int] + """Days from course start until unlock""" + + title: Optional[str] + """The title of the lesson""" diff --git a/src/whop_sdk/types/course_lesson_delete_response.py b/src/whop_sdk/types/course_lesson_delete_response.py new file mode 100644 index 00000000..8686f01f --- /dev/null +++ b/src/whop_sdk/types/course_lesson_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["CourseLessonDeleteResponse"] + +CourseLessonDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/course_lesson_list_params.py b/src/whop_sdk/types/course_lesson_list_params.py new file mode 100644 index 00000000..5f59c6e5 --- /dev/null +++ b/src/whop_sdk/types/course_lesson_list_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["CourseLessonListParams"] + + +class CourseLessonListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + chapter_id: Optional[str] + """The ID of the chapter (returns lessons only for this chapter)""" + + course_id: Optional[str] + """The ID of the course (returns all lessons across all chapters)""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" diff --git a/src/whop_sdk/types/course_lesson_list_response.py b/src/whop_sdk/types/course_lesson_list_response.py new file mode 100644 index 00000000..e7d65b22 --- /dev/null +++ b/src/whop_sdk/types/course_lesson_list_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel +from .lesson_types import LessonTypes +from .lesson_visibilities import LessonVisibilities + +__all__ = ["CourseLessonListResponse"] + + +class CourseLessonListResponse(BaseModel): + id: str + """The ID of the lesson""" + + content: Optional[str] = None + """The content of the lesson""" + + days_from_course_start_until_unlock: Optional[int] = None + """Number of days from course start until the lesson is unlocked""" + + lesson_type: LessonTypes + """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)""" + + order: int + """The order of the lesson within its chapter""" + + title: str + """The title of the lesson""" + + visibility: LessonVisibilities + """The visibility of the lesson. + + Determines how / whether this lesson is visible to users. + """ diff --git a/src/whop_sdk/types/course_lesson_update_params.py b/src/whop_sdk/types/course_lesson_update_params.py new file mode 100644 index 00000000..aca167c0 --- /dev/null +++ b/src/whop_sdk/types/course_lesson_update_params.py @@ -0,0 +1,144 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Required, TypedDict + +from .lesson_types import LessonTypes +from .lesson_visibilities import LessonVisibilities +from .assessment_question_types import AssessmentQuestionTypes + +__all__ = [ + "CourseLessonUpdateParams", + "AssessmentQuestion", + "AssessmentQuestionImage", + "AssessmentQuestionOption", + "Attachment", + "MainPdf", +] + + +class CourseLessonUpdateParams(TypedDict, total=False): + assessment_questions: Optional[Iterable[AssessmentQuestion]] + """Assessment questions for quiz/knowledge check lessons. + + Replaces all existing questions. + """ + + attachments: Optional[Iterable[Attachment]] + """General attachments for the lesson (PDFs, files, etc). + + Replaces all existing attachments. + """ + + content: Optional[str] + """The content of the lesson""" + + days_from_course_start_until_unlock: Optional[int] + """Days from course start until unlock""" + + lesson_type: Optional[LessonTypes] + """The available types for a lesson""" + + main_pdf: Optional[MainPdf] + """The main PDF file for this lesson""" + + mux_asset_id: Optional[str] + """The ID of the Mux asset to attach to this lesson for video lessons""" + + title: Optional[str] + """The title of the lesson""" + + visibility: Optional[LessonVisibilities] + """The available visibilities for a lesson. + + Determines how / whether a lesson is visible to users. + """ + + +class AssessmentQuestionImage(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ + + +class AssessmentQuestionOption(TypedDict, total=False): + is_correct: Required[bool] + """Whether this option is a correct answer""" + + option_text: Required[str] + """The text of the answer option""" + + id: Optional[str] + """The ID of an existing option. + + If provided, the option will be updated. If not provided, a new option will be + created. + """ + + +class AssessmentQuestion(TypedDict, total=False): + correct_answer: Required[str] + """The correct answer for the question. Used for short answer questions""" + + question_text: Required[str] + """The text of the question""" + + question_type: Required[AssessmentQuestionTypes] + """The type of the question""" + + id: Optional[str] + """The ID of an existing question. + + If provided, the question will be updated. If not provided, a new question will + be created. + """ + + image: Optional[AssessmentQuestionImage] + """Optional image attachment for the question""" + + options: Optional[Iterable[AssessmentQuestionOption]] + """The answer options for multiple choice/select questions""" + + +class Attachment(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ + + +class MainPdf(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ diff --git a/src/whop_sdk/types/course_list_params.py b/src/whop_sdk/types/course_list_params.py new file mode 100644 index 00000000..56734c39 --- /dev/null +++ b/src/whop_sdk/types/course_list_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["CourseListParams"] + + +class CourseListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + company_id: Optional[str] + """The ID of the company""" + + experience_id: Optional[str] + """The ID of the experience""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" diff --git a/src/whop_sdk/types/course_list_response.py b/src/whop_sdk/types/course_list_response.py new file mode 100644 index 00000000..c4358eb2 --- /dev/null +++ b/src/whop_sdk/types/course_list_response.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel +from .languages import Languages + +__all__ = ["CourseListResponse", "Thumbnail"] + + +class Thumbnail(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + optimized_url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + source_url: Optional[str] = None + """The original URL of the attachment, such as a direct link to S3. + + This should never be displayed on the client and always passed to an Imgproxy + transformer. + """ + + +class CourseListResponse(BaseModel): + id: str + """The ID of the course. Looks like cors_XXX""" + + certificate_after_completion_enabled: Optional[bool] = None + """ + Whether the course will award its students a PDF certificate after completing + all lessons + """ + + description: Optional[str] = None + """A short description of the course""" + + language: Languages + """ + The language spoken in the video content of the course, used to generate closed + captions in the right language + """ + + require_completing_lessons_in_order: bool + """ + Whether the course requires students to complete the previous lesson before + moving on to the next one + """ + + tagline: Optional[str] = None + """A short tagline for the course. + + It is displayed under the course title in the UI + """ + + thumbnail: Optional[Thumbnail] = None + """The thumbnail for the course""" + + title: Optional[str] = None + """The title of the course""" diff --git a/src/whop_sdk/types/course_update_params.py b/src/whop_sdk/types/course_update_params.py new file mode 100644 index 00000000..94b741ba --- /dev/null +++ b/src/whop_sdk/types/course_update_params.py @@ -0,0 +1,86 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Required, TypedDict + +from .languages import Languages + +__all__ = ["CourseUpdateParams", "Chapter", "ChapterLesson", "Thumbnail"] + + +class CourseUpdateParams(TypedDict, total=False): + certificate_after_completion_enabled: Optional[bool] + """ + Whether the course will award its students a PDF certificate after completing + all lessons + """ + + chapters: Optional[Iterable[Chapter]] + """The chapters and lessons to update""" + + description: Optional[str] + """A short description of the course""" + + language: Optional[Languages] + """The available languages for a course""" + + require_completing_lessons_in_order: Optional[bool] + """ + Whether the course requires students to complete the previous lesson before + moving on to the next one + """ + + tagline: Optional[str] + """A short tagline for the course""" + + thumbnail: Optional[Thumbnail] + """The thumbnail for the course in png, jpeg, or gif format""" + + title: Optional[str] + """The title of the course""" + + +class ChapterLesson(TypedDict, total=False): + id: Required[str] + """The ID of the lesson to update""" + + chapter_id: Required[str] + """The ID of the chapter this lesson belongs to (for moving between chapters)""" + + order: Required[int] + """The order of the lesson within its chapter""" + + title: Required[str] + """The title of the lesson""" + + +class Chapter(TypedDict, total=False): + id: Required[str] + """The ID of the chapter to update""" + + order: Required[int] + """The order of the chapter within its course""" + + title: Required[str] + """The title of the chapter""" + + lessons: Optional[Iterable[ChapterLesson]] + """The lessons to update within this chapter""" + + +class Thumbnail(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ diff --git a/src/whop_sdk/types/experience_duplicate_params.py b/src/whop_sdk/types/experience_duplicate_params.py new file mode 100644 index 00000000..e1dbf224 --- /dev/null +++ b/src/whop_sdk/types/experience_duplicate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ExperienceDuplicateParams"] + + +class ExperienceDuplicateParams(TypedDict, total=False): + name: Optional[str] + """The name of the new experience""" diff --git a/src/whop_sdk/types/forum_post_update_params.py b/src/whop_sdk/types/forum_post_update_params.py new file mode 100644 index 00000000..dfa2435b --- /dev/null +++ b/src/whop_sdk/types/forum_post_update_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import TypedDict + +__all__ = ["ForumPostUpdateParams", "Attachment"] + + +class ForumPostUpdateParams(TypedDict, total=False): + attachments: Optional[Iterable[Attachment]] + """The attachments for this post""" + + content: Optional[str] + """This is the main body of the post in Markdown format. + + Hidden if paywalled and user hasn't purchased access to it. + """ + + is_pinned: Optional[bool] + """Whether the post is pinned. You can only pin a top level posts (not comments).""" + + title: Optional[str] + """The title of the post. Only visible if paywalled.""" + + +class Attachment(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ diff --git a/src/whop_sdk/types/languages.py b/src/whop_sdk/types/languages.py new file mode 100644 index 00000000..d72d461e --- /dev/null +++ b/src/whop_sdk/types/languages.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["Languages"] + +Languages: TypeAlias = Literal[ + "en", + "es", + "it", + "pt", + "de", + "fr", + "pl", + "ru", + "nl", + "ca", + "tr", + "sv", + "uk", + "no", + "fi", + "sk", + "el", + "cs", + "hr", + "da", + "ro", + "bg", +] diff --git a/src/whop_sdk/types/lesson.py b/src/whop_sdk/types/lesson.py new file mode 100644 index 00000000..77aec38e --- /dev/null +++ b/src/whop_sdk/types/lesson.py @@ -0,0 +1,159 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from .._models import BaseModel +from .lesson_types import LessonTypes +from .lesson_visibilities import LessonVisibilities +from .assessment_question_types import AssessmentQuestionTypes + +__all__ = [ + "Lesson", + "AssessmentQuestion", + "AssessmentQuestionImage", + "AssessmentQuestionOption", + "Attachment", + "MainPdf", + "VideoAsset", +] + + +class AssessmentQuestionImage(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + +class AssessmentQuestionOption(BaseModel): + id: str + """The ID of the assessment question option""" + + is_correct: bool + """Whether this option is a correct answer""" + + option_text: str + """The text of the answer option""" + + order: int + """The order of this option within the question""" + + +class AssessmentQuestion(BaseModel): + id: str + """The ID of the assessment question""" + + correct_answer: str + """The correct answer for the question. Used for short answer questions""" + + created_at: datetime + """When the question was created""" + + image: Optional[AssessmentQuestionImage] = None + """Optional image attachment for the question""" + + options: List[AssessmentQuestionOption] + """The answer options for multiple choice/select questions""" + + order: int + """The order of the question within its lesson""" + + question_text: str + """The text of the question""" + + question_type: AssessmentQuestionTypes + """The type of the question""" + + +class Attachment(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + +class MainPdf(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + +class VideoAsset(BaseModel): + id: str + """The ID of the Mux asset""" + + asset_id: Optional[str] = None + """The Mux-provided ID of the asset""" + + playback_id: Optional[str] = None + """The public playback ID of the Mux asset""" + + +class Lesson(BaseModel): + id: str + """The ID of the lesson""" + + assessment_questions: List[AssessmentQuestion] + """Assessment questions for quiz/knowledge check lessons""" + + attachments: List[Attachment] + """The attached files in this lesson as a flat array""" + + content: Optional[str] = None + """The content of the lesson""" + + days_from_course_start_until_unlock: Optional[int] = None + """Number of days from course start until the lesson is unlocked""" + + lesson_type: LessonTypes + """The type of the lesson (text, video, pdf, multi, quiz, knowledge_check)""" + + main_pdf: Optional[MainPdf] = None + """The main PDF file for this lesson""" + + order: int + """The order of the lesson within its chapter""" + + title: str + """The title of the lesson""" + + video_asset: Optional[VideoAsset] = None + """The associated Mux asset for video lessons""" + + visibility: LessonVisibilities + """The visibility of the lesson. + + Determines how / whether this lesson is visible to users. + """ diff --git a/src/whop_sdk/types/lesson_types.py b/src/whop_sdk/types/lesson_types.py new file mode 100644 index 00000000..c0c4503e --- /dev/null +++ b/src/whop_sdk/types/lesson_types.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["LessonTypes"] + +LessonTypes: TypeAlias = Literal["text", "video", "pdf", "multi", "quiz", "knowledge_check"] diff --git a/src/whop_sdk/types/lesson_visibilities.py b/src/whop_sdk/types/lesson_visibilities.py new file mode 100644 index 00000000..8256e220 --- /dev/null +++ b/src/whop_sdk/types/lesson_visibilities.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["LessonVisibilities"] + +LessonVisibilities: TypeAlias = Literal["visible", "hidden"] diff --git a/src/whop_sdk/types/message_update_params.py b/src/whop_sdk/types/message_update_params.py new file mode 100644 index 00000000..703a22eb --- /dev/null +++ b/src/whop_sdk/types/message_update_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import TypedDict + +__all__ = ["MessageUpdateParams", "Attachment"] + + +class MessageUpdateParams(TypedDict, total=False): + attachments: Optional[Iterable[Attachment]] + """The attachments for this message""" + + content: Optional[str] + """The content of the message in Markdown format""" + + is_pinned: Optional[bool] + """Whether this message is pinned""" + + +class Attachment(TypedDict, total=False): + id: Optional[str] + """The ID of an existing attachment object. + + Use this when updating a resource and keeping a subset of the attachments. Don't + use this unless you know what you're doing. + """ + + direct_upload_id: Optional[str] + """This ID should be used the first time you upload an attachment. + + It is the ID of the direct upload that was created when uploading the file to S3 + via the mediaDirectUpload mutation. + """ diff --git a/src/whop_sdk/types/promo_code.py b/src/whop_sdk/types/promo_code.py new file mode 100644 index 00000000..7a03658a --- /dev/null +++ b/src/whop_sdk/types/promo_code.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel +from .promo_duration import PromoDuration +from .shared.currency import Currency +from .promo_code_status import PromoCodeStatus +from .shared.promo_type import PromoType + +__all__ = ["PromoCode", "Company", "Product"] + + +class Company(BaseModel): + id: str + """The ID of the company""" + + title: str + """The written name of the company.""" + + +class Product(BaseModel): + id: str + """The internal ID of the public product.""" + + title: str + """The title of the product. Use for Whop 4.0.""" + + +class PromoCode(BaseModel): + id: str + """The ID of the promo.""" + + amount_off: float + """The amount off (% or flat amount) for the promo.""" + + churned_users_only: bool + """Restricts promo use to only users who have churned from the company before.""" + + code: Optional[str] = None + """The specific code used to apply the promo at checkout.""" + + company: Company + """The company for the promo code.""" + + created_at: datetime + """The timestamp of when the promo was created.""" + + currency: Currency + """The monetary currency of the promo code.""" + + duration: Optional[PromoDuration] = None + """The duration setting for the promo code""" + + existing_memberships_only: bool + """Restricts promo use to only be applied to already purchased memberships.""" + + expires_at: Optional[datetime] = None + """The date/time of when the promo expires.""" + + new_users_only: bool + """ + Restricts promo use to only users who have never purchased from the company + before. + """ + + one_per_customer: bool + """Restricts promo use to only be applied once per customer.""" + + product: Optional[Product] = None + """The access pass associated with the promo code.""" + + promo_duration_months: Optional[int] = None + """The number of months the promo is applied for.""" + + promo_type: PromoType + """The type (% or flat amount) of the promo.""" + + status: PromoCodeStatus + """Indicates if the promo code is live or disabled.""" + + stock: int + """The quantity limit on the number of uses.""" + + unlimited_stock: bool + """Whether or not the promo code has unlimited stock.""" + + uses: int + """The amount of times the promo codes has been used.""" diff --git a/src/whop_sdk/types/promo_code_create_params.py b/src/whop_sdk/types/promo_code_create_params.py new file mode 100644 index 00000000..264243bb --- /dev/null +++ b/src/whop_sdk/types/promo_code_create_params.py @@ -0,0 +1,70 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo +from .shared.currency import Currency +from .shared.promo_type import PromoType + +__all__ = ["PromoCodeCreateParams"] + + +class PromoCodeCreateParams(TypedDict, total=False): + amount_off: Required[float] + """The amount off (% or flat amount) for the promo.""" + + base_currency: Required[Currency] + """The monetary currency of the promo code.""" + + code: Required[str] + """The specific code used to apply the promo at checkout.""" + + company_id: Required[str] + """The id of the company to create the promo code for.""" + + new_users_only: Required[bool] + """ + Restricts promo use to only users who have never purchased from the company + before. + """ + + promo_duration_months: Required[int] + """The number of months this promo code is applied and valid for.""" + + promo_type: Required[PromoType] + """The type (% or flat amount) of the promo.""" + + churned_users_only: Optional[bool] + """Restricts promo use to only users who have churned from the company before.""" + + existing_memberships_only: Optional[bool] + """Whether this promo code is for existing memberships only (cancelations)""" + + expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """The date/time of when the promo expires.""" + + one_per_customer: Optional[bool] + """Restricts promo use to only be applied once per customer.""" + + plan_ids: Optional[SequenceNotStr[str]] + """The IDs of the plans that the promo code applies to. + + If product_id is provided, it will only apply to plans attached to that product + """ + + product_id: Optional[str] + """The product to lock the promo code to, if any. + + If provided will filter out any plan ids not attached to this product + """ + + stock: Optional[int] + """The quantity limit on the number of uses.""" + + unlimited_stock: Optional[bool] + """Whether or not the promo code should have unlimited stock.""" diff --git a/src/whop_sdk/types/promo_code_delete_response.py b/src/whop_sdk/types/promo_code_delete_response.py new file mode 100644 index 00000000..f7939b18 --- /dev/null +++ b/src/whop_sdk/types/promo_code_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["PromoCodeDeleteResponse"] + +PromoCodeDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/promo_code_list_params.py b/src/whop_sdk/types/promo_code_list_params.py new file mode 100644 index 00000000..e632ba2d --- /dev/null +++ b/src/whop_sdk/types/promo_code_list_params.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from .._types import SequenceNotStr +from .promo_code_status import PromoCodeStatus + +__all__ = ["PromoCodeListParams"] + + +class PromoCodeListParams(TypedDict, total=False): + company_id: Required[str] + """The ID of the company to list promo codes for""" + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + plan_ids: Optional[SequenceNotStr[str]] + """Filter promo codes by plan ID(s)""" + + product_ids: Optional[SequenceNotStr[str]] + """Filter promo codes by product ID(s)""" + + status: Optional[PromoCodeStatus] + """Statuses for promo codes""" diff --git a/src/whop_sdk/types/promo_code_list_response.py b/src/whop_sdk/types/promo_code_list_response.py new file mode 100644 index 00000000..1c87acfe --- /dev/null +++ b/src/whop_sdk/types/promo_code_list_response.py @@ -0,0 +1,79 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel +from .promo_duration import PromoDuration +from .shared.currency import Currency +from .promo_code_status import PromoCodeStatus +from .shared.promo_type import PromoType + +__all__ = ["PromoCodeListResponse", "Product"] + + +class Product(BaseModel): + id: str + """The internal ID of the public product.""" + + title: str + """The title of the product. Use for Whop 4.0.""" + + +class PromoCodeListResponse(BaseModel): + id: str + """The ID of the promo.""" + + amount_off: float + """The amount off (% or flat amount) for the promo.""" + + churned_users_only: bool + """Restricts promo use to only users who have churned from the company before.""" + + code: Optional[str] = None + """The specific code used to apply the promo at checkout.""" + + created_at: datetime + """The timestamp of when the promo was created.""" + + currency: Currency + """The monetary currency of the promo code.""" + + duration: Optional[PromoDuration] = None + """The duration setting for the promo code""" + + existing_memberships_only: bool + """Restricts promo use to only be applied to already purchased memberships.""" + + expires_at: Optional[datetime] = None + """The date/time of when the promo expires.""" + + new_users_only: bool + """ + Restricts promo use to only users who have never purchased from the company + before. + """ + + one_per_customer: bool + """Restricts promo use to only be applied once per customer.""" + + product: Optional[Product] = None + """The access pass associated with the promo code.""" + + promo_duration_months: Optional[int] = None + """The number of months the promo is applied for.""" + + promo_type: PromoType + """The type (% or flat amount) of the promo.""" + + status: PromoCodeStatus + """Indicates if the promo code is live or disabled.""" + + stock: int + """The quantity limit on the number of uses.""" + + unlimited_stock: bool + """Whether or not the promo code has unlimited stock.""" + + uses: int + """The amount of times the promo codes has been used.""" diff --git a/src/whop_sdk/types/promo_code_status.py b/src/whop_sdk/types/promo_code_status.py new file mode 100644 index 00000000..e38a4a7f --- /dev/null +++ b/src/whop_sdk/types/promo_code_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["PromoCodeStatus"] + +PromoCodeStatus: TypeAlias = Literal["active", "inactive", "archived"] diff --git a/src/whop_sdk/types/promo_duration.py b/src/whop_sdk/types/promo_duration.py new file mode 100644 index 00000000..0145e77e --- /dev/null +++ b/src/whop_sdk/types/promo_duration.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["PromoDuration"] + +PromoDuration: TypeAlias = Literal["forever", "once", "repeating"] diff --git a/src/whop_sdk/types/shared_params/__init__.py b/src/whop_sdk/types/shared_params/__init__.py index c6e79558..8ed721e0 100644 --- a/src/whop_sdk/types/shared_params/__init__.py +++ b/src/whop_sdk/types/shared_params/__init__.py @@ -5,6 +5,7 @@ from .direction import Direction as Direction from .plan_type import PlanType as PlanType from .custom_cta import CustomCta as CustomCta +from .promo_type import PromoType as PromoType from .visibility import Visibility as Visibility from .access_level import AccessLevel as AccessLevel from .app_statuses import AppStatuses as AppStatuses diff --git a/src/whop_sdk/types/shared_params/promo_type.py b/src/whop_sdk/types/shared_params/promo_type.py new file mode 100644 index 00000000..098f25fe --- /dev/null +++ b/src/whop_sdk/types/shared_params/promo_type.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypeAlias + +__all__ = ["PromoType"] + +PromoType: TypeAlias = Literal["percentage", "flat_amount"] diff --git a/tests/api_resources/test_course_chapters.py b/tests/api_resources/test_course_chapters.py new file mode 100644 index 00000000..e7358f66 --- /dev/null +++ b/tests/api_resources/test_course_chapters.py @@ -0,0 +1,467 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + CourseChapter, + CourseChapterListResponse, + CourseChapterDeleteResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCourseChapters: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + course_chapter = client.course_chapters.create( + course_id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + course_chapter = client.course_chapters.create( + course_id="cors_xxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.course_chapters.with_raw_response.create( + course_id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.course_chapters.with_streaming_response.create( + course_id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + course_chapter = client.course_chapters.retrieve( + "chap_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.course_chapters.with_raw_response.retrieve( + "chap_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.course_chapters.with_streaming_response.retrieve( + "chap_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_chapters.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + course_chapter = client.course_chapters.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.course_chapters.with_raw_response.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.course_chapters.with_streaming_response.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_chapters.with_raw_response.update( + id="", + title="title", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + course_chapter = client.course_chapters.list( + course_id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + course_chapter = client.course_chapters.list( + course_id="cors_xxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + ) + assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.course_chapters.with_raw_response.list( + course_id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = response.parse() + assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.course_chapters.with_streaming_response.list( + course_id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = response.parse() + assert_matches_type(SyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + course_chapter = client.course_chapters.delete( + "chap_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.course_chapters.with_raw_response.delete( + "chap_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = response.parse() + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.course_chapters.with_streaming_response.delete( + "chap_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = response.parse() + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_chapters.with_raw_response.delete( + "", + ) + + +class TestAsyncCourseChapters: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.create( + course_id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.create( + course_id="cors_xxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.course_chapters.with_raw_response.create( + course_id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.course_chapters.with_streaming_response.create( + course_id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.retrieve( + "chap_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.course_chapters.with_raw_response.retrieve( + "chap_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.course_chapters.with_streaming_response.retrieve( + "chap_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_chapters.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.course_chapters.with_raw_response.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.course_chapters.with_streaming_response.update( + id="chap_xxxxxxxxxxxxx", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = await response.parse() + assert_matches_type(CourseChapter, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_chapters.with_raw_response.update( + id="", + title="title", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.list( + course_id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.list( + course_id="cors_xxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + ) + assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.course_chapters.with_raw_response.list( + course_id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = await response.parse() + assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.course_chapters.with_streaming_response.list( + course_id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = await response.parse() + assert_matches_type(AsyncCursorPage[CourseChapterListResponse], course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + course_chapter = await async_client.course_chapters.delete( + "chap_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.course_chapters.with_raw_response.delete( + "chap_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_chapter = await response.parse() + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.course_chapters.with_streaming_response.delete( + "chap_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_chapter = await response.parse() + assert_matches_type(CourseChapterDeleteResponse, course_chapter, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_chapters.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_course_lessons.py b/tests/api_resources/test_course_lessons.py new file mode 100644 index 00000000..dfcb126a --- /dev/null +++ b/tests/api_resources/test_course_lessons.py @@ -0,0 +1,547 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + Lesson, + CourseLessonListResponse, + CourseLessonDeleteResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCourseLessons: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + course_lesson = client.course_lessons.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + course_lesson = client.course_lessons.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + content="content", + days_from_course_start_until_unlock=42, + title="title", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.course_lessons.with_raw_response.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.course_lessons.with_streaming_response.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + course_lesson = client.course_lessons.retrieve( + "lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.course_lessons.with_raw_response.retrieve( + "lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.course_lessons.with_streaming_response.retrieve( + "lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_lessons.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + course_lesson = client.course_lessons.update( + id="lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + course_lesson = client.course_lessons.update( + id="lesn_xxxxxxxxxxxxx", + assessment_questions=[ + { + "correct_answer": "correct_answer", + "question_text": "question_text", + "question_type": "short_answer", + "id": "id", + "image": { + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + "options": [ + { + "is_correct": True, + "option_text": "option_text", + "id": "id", + } + ], + } + ], + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + days_from_course_start_until_unlock=42, + lesson_type="text", + main_pdf={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + mux_asset_id="mux_xxxxxxxxxxxxxx", + title="title", + visibility="visible", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.course_lessons.with_raw_response.update( + id="lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.course_lessons.with_streaming_response.update( + id="lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_lessons.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + course_lesson = client.course_lessons.list() + assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + course_lesson = client.course_lessons.list( + after="after", + before="before", + chapter_id="chap_xxxxxxxxxxxxx", + course_id="cors_xxxxxxxxxxxxx", + first=42, + last=42, + ) + assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.course_lessons.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = response.parse() + assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.course_lessons.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = response.parse() + assert_matches_type(SyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + course_lesson = client.course_lessons.delete( + "lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.course_lessons.with_raw_response.delete( + "lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = response.parse() + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.course_lessons.with_streaming_response.delete( + "lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = response.parse() + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.course_lessons.with_raw_response.delete( + "", + ) + + +class TestAsyncCourseLessons: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + content="content", + days_from_course_start_until_unlock=42, + title="title", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.course_lessons.with_raw_response.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.course_lessons.with_streaming_response.create( + chapter_id="chap_xxxxxxxxxxxxx", + lesson_type="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.retrieve( + "lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.course_lessons.with_raw_response.retrieve( + "lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.course_lessons.with_streaming_response.retrieve( + "lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_lessons.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.update( + id="lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.update( + id="lesn_xxxxxxxxxxxxx", + assessment_questions=[ + { + "correct_answer": "correct_answer", + "question_text": "question_text", + "question_type": "short_answer", + "id": "id", + "image": { + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + "options": [ + { + "is_correct": True, + "option_text": "option_text", + "id": "id", + } + ], + } + ], + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + days_from_course_start_until_unlock=42, + lesson_type="text", + main_pdf={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + mux_asset_id="mux_xxxxxxxxxxxxxx", + title="title", + visibility="visible", + ) + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.course_lessons.with_raw_response.update( + id="lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.course_lessons.with_streaming_response.update( + id="lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = await response.parse() + assert_matches_type(Lesson, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_lessons.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.list() + assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.list( + after="after", + before="before", + chapter_id="chap_xxxxxxxxxxxxx", + course_id="cors_xxxxxxxxxxxxx", + first=42, + last=42, + ) + assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.course_lessons.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = await response.parse() + assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.course_lessons.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = await response.parse() + assert_matches_type(AsyncCursorPage[CourseLessonListResponse], course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + course_lesson = await async_client.course_lessons.delete( + "lesn_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.course_lessons.with_raw_response.delete( + "lesn_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course_lesson = await response.parse() + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.course_lessons.with_streaming_response.delete( + "lesn_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course_lesson = await response.parse() + assert_matches_type(CourseLessonDeleteResponse, course_lesson, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.course_lessons.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_courses.py b/tests/api_resources/test_courses.py new file mode 100644 index 00000000..1a89f56e --- /dev/null +++ b/tests/api_resources/test_courses.py @@ -0,0 +1,531 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + Course, + CourseListResponse, + CourseDeleteResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCourses: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + course = client.courses.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + course = client.courses.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + tagline="tagline", + thumbnail={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.courses.with_raw_response.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.courses.with_streaming_response.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + course = client.courses.retrieve( + "cors_xxxxxxxxxxxxx", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.courses.with_raw_response.retrieve( + "cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.courses.with_streaming_response.retrieve( + "cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.courses.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + course = client.courses.update( + id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + course = client.courses.update( + id="cors_xxxxxxxxxxxxx", + certificate_after_completion_enabled=True, + chapters=[ + { + "id": "id", + "order": 42, + "title": "title", + "lessons": [ + { + "id": "id", + "chapter_id": "chap_xxxxxxxxxxxxx", + "order": 42, + "title": "title", + } + ], + } + ], + description="description", + language="en", + require_completing_lessons_in_order=True, + tagline="tagline", + thumbnail={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + title="title", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.courses.with_raw_response.update( + id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.courses.with_streaming_response.update( + id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.courses.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + course = client.courses.list() + assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + course = client.courses.list( + after="after", + before="before", + company_id="biz_xxxxxxxxxxxxxx", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + ) + assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.courses.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = response.parse() + assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.courses.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = response.parse() + assert_matches_type(SyncCursorPage[CourseListResponse], course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + course = client.courses.delete( + "cors_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.courses.with_raw_response.delete( + "cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = response.parse() + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.courses.with_streaming_response.delete( + "cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = response.parse() + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.courses.with_raw_response.delete( + "", + ) + + +class TestAsyncCourses: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + tagline="tagline", + thumbnail={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.courses.with_raw_response.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.courses.with_streaming_response.create( + experience_id="exp_xxxxxxxxxxxxxx", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.retrieve( + "cors_xxxxxxxxxxxxx", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.courses.with_raw_response.retrieve( + "cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.courses.with_streaming_response.retrieve( + "cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.courses.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.update( + id="cors_xxxxxxxxxxxxx", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.update( + id="cors_xxxxxxxxxxxxx", + certificate_after_completion_enabled=True, + chapters=[ + { + "id": "id", + "order": 42, + "title": "title", + "lessons": [ + { + "id": "id", + "chapter_id": "chap_xxxxxxxxxxxxx", + "order": 42, + "title": "title", + } + ], + } + ], + description="description", + language="en", + require_completing_lessons_in_order=True, + tagline="tagline", + thumbnail={ + "id": "id", + "direct_upload_id": "direct_upload_id", + }, + title="title", + ) + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.courses.with_raw_response.update( + id="cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.courses.with_streaming_response.update( + id="cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = await response.parse() + assert_matches_type(Course, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.courses.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.list() + assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.list( + after="after", + before="before", + company_id="biz_xxxxxxxxxxxxxx", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + ) + assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.courses.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = await response.parse() + assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.courses.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = await response.parse() + assert_matches_type(AsyncCursorPage[CourseListResponse], course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + course = await async_client.courses.delete( + "cors_xxxxxxxxxxxxx", + ) + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.courses.with_raw_response.delete( + "cors_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + course = await response.parse() + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.courses.with_streaming_response.delete( + "cors_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + course = await response.parse() + assert_matches_type(CourseDeleteResponse, course, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.courses.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_experiences.py b/tests/api_resources/test_experiences.py index 9112113c..a57c7916 100644 --- a/tests/api_resources/test_experiences.py +++ b/tests/api_resources/test_experiences.py @@ -352,6 +352,57 @@ def test_path_params_detach(self, client: Whop) -> None: product_id="prod_xxxxxxxxxxxxx", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_duplicate(self, client: Whop) -> None: + experience = client.experiences.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_duplicate_with_all_params(self, client: Whop) -> None: + experience = client.experiences.duplicate( + id="exp_xxxxxxxxxxxxxx", + name="name", + ) + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_duplicate(self, client: Whop) -> None: + response = client.experiences.with_raw_response.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = response.parse() + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_duplicate(self, client: Whop) -> None: + with client.experiences.with_streaming_response.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = response.parse() + assert_matches_type(Experience, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_duplicate(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.experiences.with_raw_response.duplicate( + id="", + ) + class TestAsyncExperiences: parametrize = pytest.mark.parametrize( @@ -687,3 +738,54 @@ async def test_path_params_detach(self, async_client: AsyncWhop) -> None: id="", product_id="prod_xxxxxxxxxxxxx", ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_duplicate(self, async_client: AsyncWhop) -> None: + experience = await async_client.experiences.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_duplicate_with_all_params(self, async_client: AsyncWhop) -> None: + experience = await async_client.experiences.duplicate( + id="exp_xxxxxxxxxxxxxx", + name="name", + ) + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_duplicate(self, async_client: AsyncWhop) -> None: + response = await async_client.experiences.with_raw_response.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = await response.parse() + assert_matches_type(Experience, experience, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_duplicate(self, async_client: AsyncWhop) -> None: + async with async_client.experiences.with_streaming_response.duplicate( + id="exp_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = await response.parse() + assert_matches_type(Experience, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_duplicate(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.experiences.with_raw_response.duplicate( + id="", + ) diff --git a/tests/api_resources/test_forum_posts.py b/tests/api_resources/test_forum_posts.py index 9487c0de..e0ef2933 100644 --- a/tests/api_resources/test_forum_posts.py +++ b/tests/api_resources/test_forum_posts.py @@ -9,7 +9,9 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import ForumPostListResponse +from whop_sdk.types import ( + ForumPostListResponse, +) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import ForumPost @@ -124,6 +126,65 @@ def test_path_params_retrieve(self, client: Whop) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + forum_post = client.forum_posts.update( + id="id", + ) + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + forum_post = client.forum_posts.update( + id="id", + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + is_pinned=True, + title="title", + ) + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.forum_posts.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + forum_post = response.parse() + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.forum_posts.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + forum_post = response.parse() + assert_matches_type(ForumPost, forum_post, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.forum_posts.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: @@ -283,6 +344,65 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + forum_post = await async_client.forum_posts.update( + id="id", + ) + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + forum_post = await async_client.forum_posts.update( + id="id", + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + is_pinned=True, + title="title", + ) + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.forum_posts.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + forum_post = await response.parse() + assert_matches_type(ForumPost, forum_post, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.forum_posts.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + forum_post = await response.parse() + assert_matches_type(ForumPost, forum_post, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.forum_posts.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index 4e79462c..17d4b01e 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -9,7 +9,9 @@ from whop_sdk import Whop, AsyncWhop from tests.utils import assert_matches_type -from whop_sdk.types import MessageListResponse +from whop_sdk.types import ( + MessageListResponse, +) from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage from whop_sdk.types.shared import Message @@ -121,6 +123,64 @@ def test_path_params_retrieve(self, client: Whop) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + message = client.messages.update( + id="id", + ) + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + message = client.messages.update( + id="id", + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + is_pinned=True, + ) + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.messages.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.messages.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(Message, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.messages.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: @@ -276,6 +336,64 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: "", ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + message = await async_client.messages.update( + id="id", + ) + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + message = await async_client.messages.update( + id="id", + attachments=[ + { + "id": "id", + "direct_upload_id": "direct_upload_id", + } + ], + content="content", + is_pinned=True, + ) + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.messages.with_raw_response.update( + id="id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = await response.parse() + assert_matches_type(Message, message, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.messages.with_streaming_response.update( + id="id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(Message, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.messages.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: diff --git a/tests/api_resources/test_promo_codes.py b/tests/api_resources/test_promo_codes.py new file mode 100644 index 00000000..db6acbf2 --- /dev/null +++ b/tests/api_resources/test_promo_codes.py @@ -0,0 +1,444 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + PromoCode, + PromoCodeListResponse, + PromoCodeDeleteResponse, +) +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPromoCodes: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Whop) -> None: + promo_code = client.promo_codes.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + promo_code = client.promo_codes.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + churned_users_only=True, + existing_memberships_only=True, + expires_at=parse_datetime("2023-12-01T05:00:00.401Z"), + one_per_customer=True, + plan_ids=["string"], + product_id="prod_xxxxxxxxxxxxx", + stock=42, + unlimited_stock=True, + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.promo_codes.with_raw_response.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.promo_codes.with_streaming_response.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + promo_code = client.promo_codes.retrieve( + "promo_xxxxxxxxxxxx", + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.promo_codes.with_raw_response.retrieve( + "promo_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.promo_codes.with_streaming_response.retrieve( + "promo_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.promo_codes.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + promo_code = client.promo_codes.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + promo_code = client.promo_codes.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + plan_ids=["string"], + product_ids=["string"], + status="active", + ) + assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.promo_codes.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = response.parse() + assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.promo_codes.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = response.parse() + assert_matches_type(SyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + promo_code = client.promo_codes.delete( + "promo_xxxxxxxxxxxx", + ) + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.promo_codes.with_raw_response.delete( + "promo_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = response.parse() + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.promo_codes.with_streaming_response.delete( + "promo_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = response.parse() + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.promo_codes.with_raw_response.delete( + "", + ) + + +class TestAsyncPromoCodes: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + churned_users_only=True, + existing_memberships_only=True, + expires_at=parse_datetime("2023-12-01T05:00:00.401Z"), + one_per_customer=True, + plan_ids=["string"], + product_id="prod_xxxxxxxxxxxxx", + stock=42, + unlimited_stock=True, + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.promo_codes.with_raw_response.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = await response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.promo_codes.with_streaming_response.create( + amount_off=6.9, + base_currency="usd", + code="code", + company_id="biz_xxxxxxxxxxxxxx", + new_users_only=True, + promo_duration_months=42, + promo_type="percentage", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = await response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.retrieve( + "promo_xxxxxxxxxxxx", + ) + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.promo_codes.with_raw_response.retrieve( + "promo_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = await response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.promo_codes.with_streaming_response.retrieve( + "promo_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = await response.parse() + assert_matches_type(PromoCode, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.promo_codes.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + plan_ids=["string"], + product_ids=["string"], + status="active", + ) + assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.promo_codes.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = await response.parse() + assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.promo_codes.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = await response.parse() + assert_matches_type(AsyncCursorPage[PromoCodeListResponse], promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + promo_code = await async_client.promo_codes.delete( + "promo_xxxxxxxxxxxx", + ) + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.promo_codes.with_raw_response.delete( + "promo_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + promo_code = await response.parse() + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.promo_codes.with_streaming_response.delete( + "promo_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + promo_code = await response.parse() + assert_matches_type(PromoCodeDeleteResponse, promo_code, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.promo_codes.with_raw_response.delete( + "", + ) From bf6a18904aaf4291f76e665be08fc39aff2f3731 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:24:31 +0000 Subject: [PATCH 08/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/experiences.py | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 71124ad0..b3c4a428 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-cc2c9659646bfa86078ad117aecdb4ad84f1dd6568cf30feb1fcbea0d99794e0.yml -openapi_spec_hash: f1c1d935bb1ab8adf977ca106da721ef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-71d651474c231bcf45ac2a7212d3952840abfa13a9897ebbadb008a0b6011b8b.yml +openapi_spec_hash: 5c2c45cbad20beee4d42eb3df2b625b3 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/resources/experiences.py b/src/whop_sdk/resources/experiences.py index 7a7bca4f..a0ae554c 100644 --- a/src/whop_sdk/resources/experiences.py +++ b/src/whop_sdk/resources/experiences.py @@ -402,7 +402,15 @@ def duplicate( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Experience: - """ + """Duplicates an existing experience. + + The name will be copied, unless provided. The + new experience will be attached to the same products as the original experience. + If duplicating a Forum or Chat experience, the new experience will have the same + settings as the original experience, e.g. who can post, who can comment, etc. No + content, e.g. posts, messages, lessons from within the original experience will + be copied. + Required permissions: - `experience:create` @@ -802,7 +810,15 @@ async def duplicate( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Experience: - """ + """Duplicates an existing experience. + + The name will be copied, unless provided. The + new experience will be attached to the same products as the original experience. + If duplicating a Forum or Chat experience, the new experience will have the same + settings as the original experience, e.g. who can post, who can comment, etc. No + content, e.g. posts, messages, lessons from within the original experience will + be copied. + Required permissions: - `experience:create` From 87085590a3ce6debdf321045f88b99ee3625ca48 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 22:41:59 +0000 Subject: [PATCH 09/20] feat(api): api update --- .stats.yml | 4 +- src/whop_sdk/resources/plans.py | 32 +++++ src/whop_sdk/types/payment_list_response.py | 139 ++++++++++++++++++-- src/whop_sdk/types/plan_create_params.py | 6 + src/whop_sdk/types/plan_list_response.py | 9 ++ src/whop_sdk/types/plan_update_params.py | 6 + src/whop_sdk/types/shared/payment.py | 139 ++++++++++++++++++-- src/whop_sdk/types/shared/plan.py | 9 ++ tests/api_resources/test_plans.py | 8 ++ 9 files changed, 324 insertions(+), 28 deletions(-) diff --git a/.stats.yml b/.stats.yml index b3c4a428..95dfb1e4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-71d651474c231bcf45ac2a7212d3952840abfa13a9897ebbadb008a0b6011b8b.yml -openapi_spec_hash: 5c2c45cbad20beee4d42eb3df2b625b3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04fbb0353a159ad866014d6bc4cceb5a1762ef7c16435a0c0192e7bc16d2270e.yml +openapi_spec_hash: 0978d1cff9874a8026df7bb427056614 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index 8327ca2e..1c1ce602 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -71,10 +71,12 @@ def create( plan_type: Optional[PlanType] | Omit = omit, release_method: Optional[ReleaseMethod] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -125,6 +127,8 @@ def create( renewal_price: The amount the customer is charged every billing period. Use only if a recurring payment. Provided as a number in dollars. Eg: 10.43 for $10.43 + stock: The number of units available for purchase. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a number in dollars. Eg: 19.99 for $19.99 @@ -135,6 +139,8 @@ def create( trial_period_days: The number of free trial days added before a renewal plan. + unlimited_stock: Limits/doesn't limit the number of units available for purchase. + visibility: Visibility of a resource extra_headers: Send extra headers @@ -163,10 +169,12 @@ def create( "plan_type": plan_type, "release_method": release_method, "renewal_price": renewal_price, + "stock": stock, "strike_through_initial_price": strike_through_initial_price, "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, + "unlimited_stock": unlimited_stock, "visibility": visibility, }, plan_create_params.PlanCreateParams, @@ -229,10 +237,12 @@ def update( offer_cancel_discount: Optional[bool] | Omit = omit, override_tax_type: Optional[TaxType] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -274,6 +284,8 @@ def update( renewal_price: The amount the customer is charged every billing period. + stock: The number of units available for purchase. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a number in dollars. Eg: 19.99 for $19.99 @@ -284,6 +296,8 @@ def update( trial_period_days: The number of free trial days added before a renewal plan. + unlimited_stock: Limits/doesn't limit the number of units available for purchase. + visibility: Visibility of a resource extra_headers: Send extra headers @@ -311,10 +325,12 @@ def update( "offer_cancel_discount": offer_cancel_discount, "override_tax_type": override_tax_type, "renewal_price": renewal_price, + "stock": stock, "strike_through_initial_price": strike_through_initial_price, "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, + "unlimited_stock": unlimited_stock, "visibility": visibility, }, plan_update_params.PlanUpdateParams, @@ -488,10 +504,12 @@ async def create( plan_type: Optional[PlanType] | Omit = omit, release_method: Optional[ReleaseMethod] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -542,6 +560,8 @@ async def create( renewal_price: The amount the customer is charged every billing period. Use only if a recurring payment. Provided as a number in dollars. Eg: 10.43 for $10.43 + stock: The number of units available for purchase. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a number in dollars. Eg: 19.99 for $19.99 @@ -552,6 +572,8 @@ async def create( trial_period_days: The number of free trial days added before a renewal plan. + unlimited_stock: Limits/doesn't limit the number of units available for purchase. + visibility: Visibility of a resource extra_headers: Send extra headers @@ -580,10 +602,12 @@ async def create( "plan_type": plan_type, "release_method": release_method, "renewal_price": renewal_price, + "stock": stock, "strike_through_initial_price": strike_through_initial_price, "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, + "unlimited_stock": unlimited_stock, "visibility": visibility, }, plan_create_params.PlanCreateParams, @@ -646,10 +670,12 @@ async def update( offer_cancel_discount: Optional[bool] | Omit = omit, override_tax_type: Optional[TaxType] | Omit = omit, renewal_price: Optional[float] | Omit = omit, + stock: Optional[int] | Omit = omit, strike_through_initial_price: Optional[float] | Omit = omit, strike_through_renewal_price: Optional[float] | Omit = omit, title: Optional[str] | Omit = omit, trial_period_days: Optional[int] | Omit = omit, + unlimited_stock: Optional[bool] | Omit = omit, visibility: Optional[Visibility] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -691,6 +717,8 @@ async def update( renewal_price: The amount the customer is charged every billing period. + stock: The number of units available for purchase. + strike_through_initial_price: The price to display with a strikethrough for the initial price. Provided as a number in dollars. Eg: 19.99 for $19.99 @@ -701,6 +729,8 @@ async def update( trial_period_days: The number of free trial days added before a renewal plan. + unlimited_stock: Limits/doesn't limit the number of units available for purchase. + visibility: Visibility of a resource extra_headers: Send extra headers @@ -728,10 +758,12 @@ async def update( "offer_cancel_discount": offer_cancel_discount, "override_tax_type": override_tax_type, "renewal_price": renewal_price, + "stock": stock, "strike_through_initial_price": strike_through_initial_price, "strike_through_renewal_price": strike_through_renewal_price, "title": title, "trial_period_days": trial_period_days, + "unlimited_stock": unlimited_stock, "visibility": visibility, }, plan_update_params.PlanUpdateParams, diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 085b75f9..1feb0a0c 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel from .shared.currency import Currency @@ -136,11 +137,34 @@ class PaymentListResponse(BaseModel): billing_address: Optional[BillingAddress] = None """The address of the user who made the payment.""" - billing_reason: Optional[str] = None - """The billing reason""" - - card_brand: Optional[str] = None - """The type of card used as the payment method.""" + billing_reason: Optional[ + Literal[ + "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" + ] + ] = None + """The reason why a specific payment was billed""" + + card_brand: Optional[ + Literal[ + "mastercard", + "visa", + "amex", + "discover", + "unionpay", + "jcb", + "diners", + "link", + "troy", + "visadankort", + "visabancontact", + "china_union_pay", + "rupay", + "jcbrupay", + "elo", + "unknown", + ] + ] = None + """Possible card brands that a payment token can have""" card_last4: Optional[str] = None """The last 4 digits of the card used to make the payment.""" @@ -172,11 +196,90 @@ class PaymentListResponse(BaseModel): paid_at: Optional[datetime] = None """The datetime the payment was paid""" - payment_method_type: Optional[str] = None - """Returns the type of payment method used for the payment, if available. - - Ex. klarna, affirm, card, cashapp - """ + payment_method_type: Optional[ + Literal[ + "acss_debit", + "affirm", + "afterpay_clearpay", + "alipay", + "alma", + "amazon_pay", + "apple_pay", + "au_becs_debit", + "bacs_debit", + "bancontact", + "billie", + "blik", + "boleto", + "card", + "cashapp", + "crypto", + "eps", + "fpx", + "giropay", + "google_pay", + "grabpay", + "ideal", + "kakao_pay", + "klarna", + "konbini", + "kr_card", + "link", + "mobilepay", + "multibanco", + "naver_pay", + "nz_bank_account", + "oxxo", + "p24", + "pay_by_bank", + "payco", + "paynow", + "pix", + "promptpay", + "revolut_pay", + "samsung_pay", + "satispay", + "sepa_debit", + "sofort", + "swish", + "twint", + "us_bank_account", + "wechat_pay", + "zip", + "bizum", + "capchase_pay", + "kriya", + "mondu", + "ng_wallet", + "paypay", + "sequra", + "scalapay", + "vipps", + "custom", + "customer_balance", + "gopay", + "mb_way", + "ng_bank", + "ng_bank_transfer", + "ng_card", + "ng_market", + "ng_ussd", + "paypal", + "payto", + "qris", + "rechnung", + "south_korea_market", + "kr_market", + "shopeepay", + "upi", + "sunbit", + "netbanking", + "id_bank_transfer", + "demo_pay", + "shop_pay", + ] + ] = None + """The different types of payment methods that can be used.""" plan: Optional[Plan] = None """The plan attached to this payment.""" @@ -188,7 +291,10 @@ class PaymentListResponse(BaseModel): """The promo code used for this payment.""" refundable: bool - """Whether the payment can be refunded.""" + """ + True only for payments that are `paid`, have not been fully refunded, and were + processed by a payment processor that allows refunds. + """ refunded_amount: Optional[float] = None """The payment refund amount(if applicable).""" @@ -197,7 +303,11 @@ class PaymentListResponse(BaseModel): """When the payment was refunded (if applicable).""" retryable: bool - """A payment can be retried if the associated membership is past due""" + """ + True when the payment status is `open` and its membership is in one of the + retry-eligible states (`active`, `trialing`, `completed`, or `past_due`); + otherwise false. Used to decide if Whop can attempt the charge again. + """ status: Optional[ReceiptStatus] = None """The status of a receipt""" @@ -218,4 +328,7 @@ class PaymentListResponse(BaseModel): """The user that made this payment.""" voidable: bool - """Whether the payment can be voided.""" + """ + True when the payment is tied to a membership in `past_due`, the payment status + is `open`, and the processor allows voiding payments; otherwise false. + """ diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index d3557ce8..d1c7dc4c 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -68,6 +68,9 @@ class PlanCreateParams(TypedDict, total=False): $10.43 """ + stock: Optional[int] + """The number of units available for purchase.""" + strike_through_initial_price: Optional[float] """The price to display with a strikethrough for the initial price. @@ -86,6 +89,9 @@ class PlanCreateParams(TypedDict, total=False): trial_period_days: Optional[int] """The number of free trial days added before a renewal plan.""" + unlimited_stock: Optional[bool] + """Limits/doesn't limit the number of units available for purchase.""" + visibility: Optional[Visibility] """Visibility of a resource""" diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 58bb0a61..213aa2fa 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -82,9 +82,18 @@ class PlanListResponse(BaseModel): renewal_price: float """The price a person has to pay for a plan on the renewal purchase.""" + stock: Optional[int] = None + """An un-used field - do not use.""" + + title: Optional[str] = None + """The title of the plan. This will be visible on the product page to customers.""" + trial_period_days: Optional[int] = None """The number of free trial days added before a renewal plan.""" + unlimited_stock: bool + """Limits/doesn't limit the number of units available for purchase.""" + updated_at: datetime """When the plan was last updated.""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index c47dc1a3..4b7ab21c 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -49,6 +49,9 @@ class PlanUpdateParams(TypedDict, total=False): renewal_price: Optional[float] """The amount the customer is charged every billing period.""" + stock: Optional[int] + """The number of units available for purchase.""" + strike_through_initial_price: Optional[float] """The price to display with a strikethrough for the initial price. @@ -67,6 +70,9 @@ class PlanUpdateParams(TypedDict, total=False): trial_period_days: Optional[int] """The number of free trial days added before a renewal plan.""" + unlimited_stock: Optional[bool] + """Limits/doesn't limit the number of units available for purchase.""" + visibility: Optional[Visibility] """Visibility of a resource""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 57ceaaba..09f5ac86 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from .currency import Currency from ..._models import BaseModel @@ -126,11 +127,34 @@ class Payment(BaseModel): billing_address: Optional[BillingAddress] = None """The address of the user who made the payment.""" - billing_reason: Optional[str] = None - """The billing reason""" - - card_brand: Optional[str] = None - """The type of card used as the payment method.""" + billing_reason: Optional[ + Literal[ + "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" + ] + ] = None + """The reason why a specific payment was billed""" + + card_brand: Optional[ + Literal[ + "mastercard", + "visa", + "amex", + "discover", + "unionpay", + "jcb", + "diners", + "link", + "troy", + "visadankort", + "visabancontact", + "china_union_pay", + "rupay", + "jcbrupay", + "elo", + "unknown", + ] + ] = None + """Possible card brands that a payment token can have""" card_last4: Optional[str] = None """The last 4 digits of the card used to make the payment.""" @@ -162,11 +186,90 @@ class Payment(BaseModel): paid_at: Optional[datetime] = None """The datetime the payment was paid""" - payment_method_type: Optional[str] = None - """Returns the type of payment method used for the payment, if available. - - Ex. klarna, affirm, card, cashapp - """ + payment_method_type: Optional[ + Literal[ + "acss_debit", + "affirm", + "afterpay_clearpay", + "alipay", + "alma", + "amazon_pay", + "apple_pay", + "au_becs_debit", + "bacs_debit", + "bancontact", + "billie", + "blik", + "boleto", + "card", + "cashapp", + "crypto", + "eps", + "fpx", + "giropay", + "google_pay", + "grabpay", + "ideal", + "kakao_pay", + "klarna", + "konbini", + "kr_card", + "link", + "mobilepay", + "multibanco", + "naver_pay", + "nz_bank_account", + "oxxo", + "p24", + "pay_by_bank", + "payco", + "paynow", + "pix", + "promptpay", + "revolut_pay", + "samsung_pay", + "satispay", + "sepa_debit", + "sofort", + "swish", + "twint", + "us_bank_account", + "wechat_pay", + "zip", + "bizum", + "capchase_pay", + "kriya", + "mondu", + "ng_wallet", + "paypay", + "sequra", + "scalapay", + "vipps", + "custom", + "customer_balance", + "gopay", + "mb_way", + "ng_bank", + "ng_bank_transfer", + "ng_card", + "ng_market", + "ng_ussd", + "paypal", + "payto", + "qris", + "rechnung", + "south_korea_market", + "kr_market", + "shopeepay", + "upi", + "sunbit", + "netbanking", + "id_bank_transfer", + "demo_pay", + "shop_pay", + ] + ] = None + """The different types of payment methods that can be used.""" plan: Optional[Plan] = None """The plan attached to this payment.""" @@ -178,7 +281,10 @@ class Payment(BaseModel): """The promo code used for this payment.""" refundable: bool - """Whether the payment can be refunded.""" + """ + True only for payments that are `paid`, have not been fully refunded, and were + processed by a payment processor that allows refunds. + """ refunded_amount: Optional[float] = None """The payment refund amount(if applicable).""" @@ -187,7 +293,11 @@ class Payment(BaseModel): """When the payment was refunded (if applicable).""" retryable: bool - """A payment can be retried if the associated membership is past due""" + """ + True when the payment status is `open` and its membership is in one of the + retry-eligible states (`active`, `trialing`, `completed`, or `past_due`); + otherwise false. Used to decide if Whop can attempt the charge again. + """ status: Optional[ReceiptStatus] = None """The status of a receipt""" @@ -208,4 +318,7 @@ class Payment(BaseModel): """The user that made this payment.""" voidable: bool - """Whether the payment can be voided.""" + """ + True when the payment is tied to a membership in `past_due`, the payment status + is `open`, and the processor allows voiding payments; otherwise false. + """ diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index c55eca08..f82ae0d3 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -110,12 +110,21 @@ class Plan(BaseModel): renewal_price: float """The price a person has to pay for a plan on the renewal purchase.""" + stock: Optional[int] = None + """An un-used field - do not use.""" + tax_type: TaxType """The tax type for the plan.""" + title: Optional[str] = None + """The title of the plan. This will be visible on the product page to customers.""" + trial_period_days: Optional[int] = None """The number of free trial days added before a renewal plan.""" + unlimited_stock: bool + """Limits/doesn't limit the number of units available for purchase.""" + updated_at: datetime """When the plan was last updated.""" diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 4e5a1d5a..c77368a8 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -61,10 +61,12 @@ def test_method_create_with_all_params(self, client: Whop) -> None: plan_type="renewal", release_method="buy_now", renewal_price=6.9, + stock=42, strike_through_initial_price=6.9, strike_through_renewal_price=6.9, title="title", trial_period_days=42, + unlimited_stock=True, visibility="visible", ) assert_matches_type(Plan, plan, path=["response"]) @@ -175,10 +177,12 @@ def test_method_update_with_all_params(self, client: Whop) -> None: offer_cancel_discount=True, override_tax_type="inclusive", renewal_price=6.9, + stock=42, strike_through_initial_price=6.9, strike_through_renewal_price=6.9, title="title", trial_period_days=42, + unlimited_stock=True, visibility="visible", ) assert_matches_type(Plan, plan, path=["response"]) @@ -356,10 +360,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N plan_type="renewal", release_method="buy_now", renewal_price=6.9, + stock=42, strike_through_initial_price=6.9, strike_through_renewal_price=6.9, title="title", trial_period_days=42, + unlimited_stock=True, visibility="visible", ) assert_matches_type(Plan, plan, path=["response"]) @@ -470,10 +476,12 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N offer_cancel_discount=True, override_tax_type="inclusive", renewal_price=6.9, + stock=42, strike_through_initial_price=6.9, strike_through_renewal_price=6.9, title="title", trial_period_days=42, + unlimited_stock=True, visibility="visible", ) assert_matches_type(Plan, plan, path=["response"]) From 06fcd76e5f7c84b1d6a2dda07464867261ea9b58 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 00:24:21 +0000 Subject: [PATCH 10/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/plan_list_response.py | 2 +- src/whop_sdk/types/shared/plan.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 95dfb1e4..986789a2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04fbb0353a159ad866014d6bc4cceb5a1762ef7c16435a0c0192e7bc16d2270e.yml -openapi_spec_hash: 0978d1cff9874a8026df7bb427056614 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04b8ad26c5d9a50ccdccca43b7cfeb43a9f2c4326d54d6f228b061f27afe79a5.yml +openapi_spec_hash: e07a797bd41fe9fd673602f78c883486 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 213aa2fa..476da8f3 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -83,7 +83,7 @@ class PlanListResponse(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """An un-used field - do not use.""" + """The number of units available for purchase. Only displayed to authorized actors""" title: Optional[str] = None """The title of the plan. This will be visible on the product page to customers.""" diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index f82ae0d3..10175a0c 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -111,7 +111,7 @@ class Plan(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """An un-used field - do not use.""" + """The number of units available for purchase. Only displayed to authorized actors""" tax_type: TaxType """The tax type for the plan.""" From b8cb3de04d4122a4054a1f56cfee7637a0a1d0af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 01:24:16 +0000 Subject: [PATCH 11/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/plan_list_response.py | 2 +- src/whop_sdk/types/shared/plan.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 986789a2..95dfb1e4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04b8ad26c5d9a50ccdccca43b7cfeb43a9f2c4326d54d6f228b061f27afe79a5.yml -openapi_spec_hash: e07a797bd41fe9fd673602f78c883486 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04fbb0353a159ad866014d6bc4cceb5a1762ef7c16435a0c0192e7bc16d2270e.yml +openapi_spec_hash: 0978d1cff9874a8026df7bb427056614 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 476da8f3..213aa2fa 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -83,7 +83,7 @@ class PlanListResponse(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """The number of units available for purchase. Only displayed to authorized actors""" + """An un-used field - do not use.""" title: Optional[str] = None """The title of the plan. This will be visible on the product page to customers.""" diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index 10175a0c..f82ae0d3 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -111,7 +111,7 @@ class Plan(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """The number of units available for purchase. Only displayed to authorized actors""" + """An un-used field - do not use.""" tax_type: TaxType """The tax type for the plan.""" From 853834f283692e15c9539869b157fb42244ed8b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 15:24:21 +0000 Subject: [PATCH 12/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/courses.py | 16 ++++++++++++++++ src/whop_sdk/types/course_create_params.py | 3 +++ src/whop_sdk/types/course_update_params.py | 3 +++ tests/api_resources/test_courses.py | 4 ++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 95dfb1e4..81e796e6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-04fbb0353a159ad866014d6bc4cceb5a1762ef7c16435a0c0192e7bc16d2270e.yml -openapi_spec_hash: 0978d1cff9874a8026df7bb427056614 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-79ac456f35ff2640a2f8e925aa51ea240612bb7961b1dc77da788ca9264918f2.yml +openapi_spec_hash: 4a95406edcacc78c6f4fe0670ad4d2e1 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/resources/courses.py b/src/whop_sdk/resources/courses.py index 93de3c21..c760845f 100644 --- a/src/whop_sdk/resources/courses.py +++ b/src/whop_sdk/resources/courses.py @@ -52,6 +52,7 @@ def create( *, experience_id: str, title: str, + cover_image: Optional[str] | Omit = omit, tagline: Optional[str] | Omit = omit, thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -73,6 +74,8 @@ def create( title: The title of the course + cover_image: The cover image URL of the course + tagline: The tagline of the course thumbnail: The thumbnail for the course in png, jpeg, or gif format @@ -91,6 +94,7 @@ def create( { "experience_id": experience_id, "title": title, + "cover_image": cover_image, "tagline": tagline, "thumbnail": thumbnail, }, @@ -145,6 +149,7 @@ def update( *, certificate_after_completion_enabled: Optional[bool] | Omit = omit, chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit, + cover_image: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, language: Optional[Languages] | Omit = omit, require_completing_lessons_in_order: Optional[bool] | Omit = omit, @@ -171,6 +176,8 @@ def update( chapters: The chapters and lessons to update + cover_image: The cover image URL of the course + description: A short description of the course language: The available languages for a course @@ -200,6 +207,7 @@ def update( { "certificate_after_completion_enabled": certificate_after_completion_enabled, "chapters": chapters, + "cover_image": cover_image, "description": description, "language": language, "require_completing_lessons_in_order": require_completing_lessons_in_order, @@ -345,6 +353,7 @@ async def create( *, experience_id: str, title: str, + cover_image: Optional[str] | Omit = omit, tagline: Optional[str] | Omit = omit, thumbnail: Optional[course_create_params.Thumbnail] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -366,6 +375,8 @@ async def create( title: The title of the course + cover_image: The cover image URL of the course + tagline: The tagline of the course thumbnail: The thumbnail for the course in png, jpeg, or gif format @@ -384,6 +395,7 @@ async def create( { "experience_id": experience_id, "title": title, + "cover_image": cover_image, "tagline": tagline, "thumbnail": thumbnail, }, @@ -438,6 +450,7 @@ async def update( *, certificate_after_completion_enabled: Optional[bool] | Omit = omit, chapters: Optional[Iterable[course_update_params.Chapter]] | Omit = omit, + cover_image: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, language: Optional[Languages] | Omit = omit, require_completing_lessons_in_order: Optional[bool] | Omit = omit, @@ -464,6 +477,8 @@ async def update( chapters: The chapters and lessons to update + cover_image: The cover image URL of the course + description: A short description of the course language: The available languages for a course @@ -493,6 +508,7 @@ async def update( { "certificate_after_completion_enabled": certificate_after_completion_enabled, "chapters": chapters, + "cover_image": cover_image, "description": description, "language": language, "require_completing_lessons_in_order": require_completing_lessons_in_order, diff --git a/src/whop_sdk/types/course_create_params.py b/src/whop_sdk/types/course_create_params.py index a5cedada..dc86f657 100644 --- a/src/whop_sdk/types/course_create_params.py +++ b/src/whop_sdk/types/course_create_params.py @@ -15,6 +15,9 @@ class CourseCreateParams(TypedDict, total=False): title: Required[str] """The title of the course""" + cover_image: Optional[str] + """The cover image URL of the course""" + tagline: Optional[str] """The tagline of the course""" diff --git a/src/whop_sdk/types/course_update_params.py b/src/whop_sdk/types/course_update_params.py index 94b741ba..35353cc3 100644 --- a/src/whop_sdk/types/course_update_params.py +++ b/src/whop_sdk/types/course_update_params.py @@ -20,6 +20,9 @@ class CourseUpdateParams(TypedDict, total=False): chapters: Optional[Iterable[Chapter]] """The chapters and lessons to update""" + cover_image: Optional[str] + """The cover image URL of the course""" + description: Optional[str] """A short description of the course""" diff --git a/tests/api_resources/test_courses.py b/tests/api_resources/test_courses.py index 1a89f56e..9d827d67 100644 --- a/tests/api_resources/test_courses.py +++ b/tests/api_resources/test_courses.py @@ -37,6 +37,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: course = client.courses.create( experience_id="exp_xxxxxxxxxxxxxx", title="title", + cover_image="cover_image", tagline="tagline", thumbnail={ "id": "id", @@ -144,6 +145,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: ], } ], + cover_image="cover_image", description="description", language="en", require_completing_lessons_in_order=True, @@ -294,6 +296,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N course = await async_client.courses.create( experience_id="exp_xxxxxxxxxxxxxx", title="title", + cover_image="cover_image", tagline="tagline", thumbnail={ "id": "id", @@ -401,6 +404,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N ], } ], + cover_image="cover_image", description="description", language="en", require_completing_lessons_in_order=True, From 88d7744347e459d509142bf983770cb5086b06c9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:24:25 +0000 Subject: [PATCH 13/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/checkout_configuration_list_response.py | 5 ++++- src/whop_sdk/types/shared/checkout_configuration.py | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 81e796e6..03eb5979 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-79ac456f35ff2640a2f8e925aa51ea240612bb7961b1dc77da788ca9264918f2.yml -openapi_spec_hash: 4a95406edcacc78c6f4fe0670ad4d2e1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-3e800078cd195eb00c2323852f4367db4f6c08ab52a71865f84d212fa14b51a8.yml +openapi_spec_hash: cb793cb78f7551f8ee89c25eb61b2b90 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/types/checkout_configuration_list_response.py b/src/whop_sdk/types/checkout_configuration_list_response.py index 07c1f4b2..76980021 100644 --- a/src/whop_sdk/types/checkout_configuration_list_response.py +++ b/src/whop_sdk/types/checkout_configuration_list_response.py @@ -60,7 +60,10 @@ class CheckoutConfigurationListResponse(BaseModel): """The plan to use for the checkout configuration""" purchase_url: str - """The URL to redirect the user to after the checkout configuration is created""" + """A URL you can send to customers to complete a checkout. + + It looks like `/checkout/plan_xxxx?session={id}` + """ redirect_url: Optional[str] = None """The URL to redirect the user to after the checkout configuration is created""" diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py index 0812fa47..6a139a81 100644 --- a/src/whop_sdk/types/shared/checkout_configuration.py +++ b/src/whop_sdk/types/shared/checkout_configuration.py @@ -60,7 +60,10 @@ class CheckoutConfiguration(BaseModel): """The plan to use for the checkout configuration""" purchase_url: str - """The URL to redirect the user to after the checkout configuration is created""" + """A URL you can send to customers to complete a checkout. + + It looks like `/checkout/plan_xxxx?session={id}` + """ redirect_url: Optional[str] = None """The URL to redirect the user to after the checkout configuration is created""" From e4d5e222ffc757b3255624c5824abcdc8312ea37 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:24:29 +0000 Subject: [PATCH 14/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/members.py | 8 ++++++++ src/whop_sdk/resources/memberships.py | 8 ++++++++ src/whop_sdk/types/member_list_params.py | 3 +++ src/whop_sdk/types/membership_list_params.py | 3 +++ src/whop_sdk/types/plan_list_response.py | 2 +- src/whop_sdk/types/shared/plan.py | 2 +- tests/api_resources/test_members.py | 2 ++ tests/api_resources/test_memberships.py | 2 ++ 9 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 03eb5979..d1115137 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-3e800078cd195eb00c2323852f4367db4f6c08ab52a71865f84d212fa14b51a8.yml -openapi_spec_hash: cb793cb78f7551f8ee89c25eb61b2b90 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-f096b77c6d622f588b5ef2c365c7af345b5e9cd00d8693f846870594be53f936.yml +openapi_spec_hash: 63d4bcc1c8d5f821a5855267127fcf2a config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/resources/members.py b/src/whop_sdk/resources/members.py index 5cf6f0cf..682337b8 100644 --- a/src/whop_sdk/resources/members.py +++ b/src/whop_sdk/resources/members.py @@ -110,6 +110,7 @@ def list( promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit, query: Optional[str] | Omit = omit, statuses: Optional[List[MemberStatuses]] | Omit = omit, + user_ids: Optional[SequenceNotStr[str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -160,6 +161,8 @@ def list( statuses: The statuses to filter the members by + user_ids: The user IDs to filter the members by + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -194,6 +197,7 @@ def list( "promo_code_ids": promo_code_ids, "query": query, "statuses": statuses, + "user_ids": user_ids, }, member_list_params.MemberListParams, ), @@ -281,6 +285,7 @@ def list( promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit, query: Optional[str] | Omit = omit, statuses: Optional[List[MemberStatuses]] | Omit = omit, + user_ids: Optional[SequenceNotStr[str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -331,6 +336,8 @@ def list( statuses: The statuses to filter the members by + user_ids: The user IDs to filter the members by + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -365,6 +372,7 @@ def list( "promo_code_ids": promo_code_ids, "query": query, "statuses": statuses, + "user_ids": user_ids, }, member_list_params.MemberListParams, ), diff --git a/src/whop_sdk/resources/memberships.py b/src/whop_sdk/resources/memberships.py index 907f9efa..67f0c390 100644 --- a/src/whop_sdk/resources/memberships.py +++ b/src/whop_sdk/resources/memberships.py @@ -164,6 +164,7 @@ def list( plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit, statuses: Optional[List[MembershipStatus]] | Omit = omit, + user_ids: Optional[SequenceNotStr[str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -207,6 +208,8 @@ def list( statuses: The membership status to filter the memberships by + user_ids: Only return memberships from these whop user ids + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -239,6 +242,7 @@ def list( "plan_ids": plan_ids, "promo_code_ids": promo_code_ids, "statuses": statuses, + "user_ids": user_ids, }, membership_list_params.MembershipListParams, ), @@ -503,6 +507,7 @@ def list( plan_ids: Optional[SequenceNotStr[str]] | Omit = omit, promo_code_ids: Optional[SequenceNotStr[str]] | Omit = omit, statuses: Optional[List[MembershipStatus]] | Omit = omit, + user_ids: Optional[SequenceNotStr[str]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -546,6 +551,8 @@ def list( statuses: The membership status to filter the memberships by + user_ids: Only return memberships from these whop user ids + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -578,6 +585,7 @@ def list( "plan_ids": plan_ids, "promo_code_ids": promo_code_ids, "statuses": statuses, + "user_ids": user_ids, }, membership_list_params.MembershipListParams, ), diff --git a/src/whop_sdk/types/member_list_params.py b/src/whop_sdk/types/member_list_params.py index f139719e..157e6a21 100644 --- a/src/whop_sdk/types/member_list_params.py +++ b/src/whop_sdk/types/member_list_params.py @@ -68,3 +68,6 @@ class MemberListParams(TypedDict, total=False): statuses: Optional[List[MemberStatuses]] """The statuses to filter the members by""" + + user_ids: Optional[SequenceNotStr[str]] + """The user IDs to filter the members by""" diff --git a/src/whop_sdk/types/membership_list_params.py b/src/whop_sdk/types/membership_list_params.py index e1547809..82e89091 100644 --- a/src/whop_sdk/types/membership_list_params.py +++ b/src/whop_sdk/types/membership_list_params.py @@ -68,3 +68,6 @@ class MembershipListParams(TypedDict, total=False): statuses: Optional[List[MembershipStatus]] """The membership status to filter the memberships by""" + + user_ids: Optional[SequenceNotStr[str]] + """Only return memberships from these whop user ids""" diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 213aa2fa..476da8f3 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -83,7 +83,7 @@ class PlanListResponse(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """An un-used field - do not use.""" + """The number of units available for purchase. Only displayed to authorized actors""" title: Optional[str] = None """The title of the plan. This will be visible on the product page to customers.""" diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index f82ae0d3..10175a0c 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -111,7 +111,7 @@ class Plan(BaseModel): """The price a person has to pay for a plan on the renewal purchase.""" stock: Optional[int] = None - """An un-used field - do not use.""" + """The number of units available for purchase. Only displayed to authorized actors""" tax_type: TaxType """The tax type for the plan.""" diff --git a/tests/api_resources/test_members.py b/tests/api_resources/test_members.py index fc600750..05887df1 100644 --- a/tests/api_resources/test_members.py +++ b/tests/api_resources/test_members.py @@ -89,6 +89,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: promo_code_ids=["string"], query="query", statuses=["drafted"], + user_ids=["string"], ) assert_matches_type(SyncCursorPage[MemberListResponse], member, path=["response"]) @@ -194,6 +195,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non promo_code_ids=["string"], query="query", statuses=["drafted"], + user_ids=["string"], ) assert_matches_type(AsyncCursorPage[MemberListResponse], member, path=["response"]) diff --git a/tests/api_resources/test_memberships.py b/tests/api_resources/test_memberships.py index f0edf0ab..971f3674 100644 --- a/tests/api_resources/test_memberships.py +++ b/tests/api_resources/test_memberships.py @@ -141,6 +141,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: plan_ids=["string"], promo_code_ids=["string"], statuses=["trialing"], + user_ids=["string"], ) assert_matches_type(SyncCursorPage[MembershipListResponse], membership, path=["response"]) @@ -439,6 +440,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non plan_ids=["string"], promo_code_ids=["string"], statuses=["trialing"], + user_ids=["string"], ) assert_matches_type(AsyncCursorPage[MembershipListResponse], membership, path=["response"]) From fb03516f3ca1c79848e16fbf21f780fb76db226d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:55:52 +0000 Subject: [PATCH 15/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/payment_list_response.py | 5 +++++ src/whop_sdk/types/shared/payment.py | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d1115137..c8994afe 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-f096b77c6d622f588b5ef2c365c7af345b5e9cd00d8693f846870594be53f936.yml -openapi_spec_hash: 63d4bcc1c8d5f821a5855267127fcf2a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-a8c95bc68640e6f3db562d95a65489a26e199a1f9d6b41d443a17bb139c0afca.yml +openapi_spec_hash: 6390779aa9c24173a39e17fa560ac259 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 1feb0a0c..04f46e9c 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -277,6 +277,11 @@ class PaymentListResponse(BaseModel): "id_bank_transfer", "demo_pay", "shop_pay", + "apple", + "sezzle", + "coinbase", + "splitit", + "unknown", ] ] = None """The different types of payment methods that can be used.""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 09f5ac86..7a042e7e 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -267,6 +267,11 @@ class Payment(BaseModel): "id_bank_transfer", "demo_pay", "shop_pay", + "apple", + "sezzle", + "coinbase", + "splitit", + "unknown", ] ] = None """The different types of payment methods that can be used.""" From 9d1c491cc58672b4d7bb54d14717704b5dda8d73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:24:35 +0000 Subject: [PATCH 16/20] feat(api): api update --- .stats.yml | 4 +-- src/whop_sdk/resources/memberships.py | 16 +++++------ src/whop_sdk/types/membership_list_params.py | 8 +++--- tests/api_resources/test_memberships.py | 28 ++++++-------------- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8994afe..806b71fb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-a8c95bc68640e6f3db562d95a65489a26e199a1f9d6b41d443a17bb139c0afca.yml -openapi_spec_hash: 6390779aa9c24173a39e17fa560ac259 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ea27cbf72ab9df835b72bf186778d2aaf032ab4f37753354dd2cb5aef977f5a2.yml +openapi_spec_hash: 00ec1c4656399eeb8e3583d3da28e4d1 config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/resources/memberships.py b/src/whop_sdk/resources/memberships.py index 67f0c390..dd593d22 100644 --- a/src/whop_sdk/resources/memberships.py +++ b/src/whop_sdk/resources/memberships.py @@ -136,7 +136,6 @@ def update( def list( self, *, - company_id: str, access_pass_ids: Optional[SequenceNotStr[str]] | Omit = omit, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, @@ -154,6 +153,7 @@ def list( ] ] | Omit = omit, + company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, direction: Optional[Direction] | Omit = omit, @@ -180,8 +180,6 @@ def list( - `member:basic:read` Args: - company_id: The ID of the company to list memberships for - access_pass_ids: The access pass IDs to filter the memberships by after: Returns the elements in the list that come after the specified cursor. @@ -190,6 +188,8 @@ def list( cancel_options: The cancel options to filter the memberships by + company_id: The ID of the company to list memberships for + created_after: The minimum creation date to filter by created_before: The maximum creation date to filter by @@ -228,11 +228,11 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "access_pass_ids": access_pass_ids, "after": after, "before": before, "cancel_options": cancel_options, + "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, @@ -479,7 +479,6 @@ async def update( def list( self, *, - company_id: str, access_pass_ids: Optional[SequenceNotStr[str]] | Omit = omit, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, @@ -497,6 +496,7 @@ def list( ] ] | Omit = omit, + company_id: Optional[str] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, direction: Optional[Direction] | Omit = omit, @@ -523,8 +523,6 @@ def list( - `member:basic:read` Args: - company_id: The ID of the company to list memberships for - access_pass_ids: The access pass IDs to filter the memberships by after: Returns the elements in the list that come after the specified cursor. @@ -533,6 +531,8 @@ def list( cancel_options: The cancel options to filter the memberships by + company_id: The ID of the company to list memberships for + created_after: The minimum creation date to filter by created_before: The maximum creation date to filter by @@ -571,11 +571,11 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "access_pass_ids": access_pass_ids, "after": after, "before": before, "cancel_options": cancel_options, + "company_id": company_id, "created_after": created_after, "created_before": created_before, "direction": direction, diff --git a/src/whop_sdk/types/membership_list_params.py b/src/whop_sdk/types/membership_list_params.py index 82e89091..f57898e1 100644 --- a/src/whop_sdk/types/membership_list_params.py +++ b/src/whop_sdk/types/membership_list_params.py @@ -4,7 +4,7 @@ from typing import List, Union, Optional from datetime import datetime -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._types import SequenceNotStr from .._utils import PropertyInfo @@ -15,9 +15,6 @@ class MembershipListParams(TypedDict, total=False): - company_id: Required[str] - """The ID of the company to list memberships for""" - access_pass_ids: Optional[SequenceNotStr[str]] """The access pass IDs to filter the memberships by""" @@ -42,6 +39,9 @@ class MembershipListParams(TypedDict, total=False): ] """The cancel options to filter the memberships by""" + company_id: Optional[str] + """The ID of the company to list memberships for""" + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """The minimum creation date to filter by""" diff --git a/tests/api_resources/test_memberships.py b/tests/api_resources/test_memberships.py index 971f3674..7423dd0e 100644 --- a/tests/api_resources/test_memberships.py +++ b/tests/api_resources/test_memberships.py @@ -118,20 +118,18 @@ def test_path_params_update(self, client: Whop) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: - membership = client.memberships.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + membership = client.memberships.list() assert_matches_type(SyncCursorPage[MembershipListResponse], membership, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: membership = client.memberships.list( - company_id="biz_xxxxxxxxxxxxxx", access_pass_ids=["string"], after="after", before="before", cancel_options=["too_expensive"], + company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", @@ -148,9 +146,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: Whop) -> None: - response = client.memberships.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = client.memberships.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -160,9 +156,7 @@ def test_raw_response_list(self, client: Whop) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: Whop) -> None: - with client.memberships.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + with client.memberships.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -417,20 +411,18 @@ async def test_path_params_update(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: - membership = await async_client.memberships.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + membership = await async_client.memberships.list() assert_matches_type(AsyncCursorPage[MembershipListResponse], membership, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: membership = await async_client.memberships.list( - company_id="biz_xxxxxxxxxxxxxx", access_pass_ids=["string"], after="after", before="before", cancel_options=["too_expensive"], + company_id="biz_xxxxxxxxxxxxxx", created_after=parse_datetime("2023-12-01T05:00:00.401Z"), created_before=parse_datetime("2023-12-01T05:00:00.401Z"), direction="asc", @@ -447,9 +439,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: - response = await async_client.memberships.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = await async_client.memberships.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -459,9 +449,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: - async with async_client.memberships.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + async with async_client.memberships.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From d8da83c3d38e6944f96d8f1ac871988ecea8ebb9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:24:44 +0000 Subject: [PATCH 17/20] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 806b71fb..19228f26 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ea27cbf72ab9df835b72bf186778d2aaf032ab4f37753354dd2cb5aef977f5a2.yml -openapi_spec_hash: 00ec1c4656399eeb8e3583d3da28e4d1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e9284b7c48fa08b48750cd2cba4271b076024011e1d8e537eb90e7586924ab79.yml +openapi_spec_hash: 070d6434a6e9912c3a3b177ed5f8b016 config_hash: a83108370c39c0ce1b66dbe018cdd484 From cb26567d0790a6b14a82a7b031e6319bc467af03 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:24:37 +0000 Subject: [PATCH 18/20] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/course.py | 7 +++++++ src/whop_sdk/types/course_list_response.py | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 19228f26..a2eb2c9a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 103 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e9284b7c48fa08b48750cd2cba4271b076024011e1d8e537eb90e7586924ab79.yml -openapi_spec_hash: 070d6434a6e9912c3a3b177ed5f8b016 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ef12082e1e61bb15be49ade1364442c5c96bf93c23614e7b39142a8521fd02b7.yml +openapi_spec_hash: c242c4c29807f5967f6ea1247e199dff config_hash: a83108370c39c0ce1b66dbe018cdd484 diff --git a/src/whop_sdk/types/course.py b/src/whop_sdk/types/course.py index 84fd76a9..f39808de 100644 --- a/src/whop_sdk/types/course.py +++ b/src/whop_sdk/types/course.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from .._models import BaseModel from .languages import Languages @@ -74,6 +75,9 @@ class Course(BaseModel): chapters: List[Chapter] """The chapters in this course""" + created_at: datetime + """The timestamp of when the course was created""" + description: Optional[str] = None """A short description of the course""" @@ -100,3 +104,6 @@ class Course(BaseModel): title: Optional[str] = None """The title of the course""" + + updated_at: datetime + """The timestamp of when the course was last updated""" diff --git a/src/whop_sdk/types/course_list_response.py b/src/whop_sdk/types/course_list_response.py index c4358eb2..ea312354 100644 --- a/src/whop_sdk/types/course_list_response.py +++ b/src/whop_sdk/types/course_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from .._models import BaseModel from .languages import Languages @@ -42,6 +43,9 @@ class CourseListResponse(BaseModel): all lessons """ + created_at: datetime + """The timestamp of when the course was created""" + description: Optional[str] = None """A short description of the course""" @@ -68,3 +72,6 @@ class CourseListResponse(BaseModel): title: Optional[str] = None """The title of the course""" + + updated_at: datetime + """The timestamp of when the course was last updated""" From 981c0dfb7441c2322c75e2dd6620b5064dd294ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:46:04 +0000 Subject: [PATCH 19/20] feat(api): manual updates --- .stats.yml | 4 +- api.md | 15 +- src/whop_sdk/_client.py | 13 +- src/whop_sdk/resources/__init__.py | 14 + src/whop_sdk/resources/payments.py | 29 +- src/whop_sdk/resources/reviews.py | 315 ++++++++++++++++++ src/whop_sdk/types/__init__.py | 7 + src/whop_sdk/types/billing_reasons.py | 9 + src/whop_sdk/types/card_brands.py | 24 ++ src/whop_sdk/types/payment_list_params.py | 9 +- src/whop_sdk/types/payment_list_response.py | 120 +------ src/whop_sdk/types/payment_method_types.py | 92 +++++ src/whop_sdk/types/review_list_params.py | 31 ++ src/whop_sdk/types/review_list_response.py | 78 +++++ .../types/review_retrieve_response.py | 103 ++++++ src/whop_sdk/types/review_status.py | 7 + src/whop_sdk/types/shared/payment.py | 120 +------ tests/api_resources/test_reviews.py | 205 ++++++++++++ 18 files changed, 929 insertions(+), 266 deletions(-) create mode 100644 src/whop_sdk/resources/reviews.py create mode 100644 src/whop_sdk/types/billing_reasons.py create mode 100644 src/whop_sdk/types/card_brands.py create mode 100644 src/whop_sdk/types/payment_method_types.py create mode 100644 src/whop_sdk/types/review_list_params.py create mode 100644 src/whop_sdk/types/review_list_response.py create mode 100644 src/whop_sdk/types/review_retrieve_response.py create mode 100644 src/whop_sdk/types/review_status.py create mode 100644 tests/api_resources/test_reviews.py diff --git a/.stats.yml b/.stats.yml index a2eb2c9a..1125bba4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 103 +configured_endpoints: 105 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ef12082e1e61bb15be49ade1364442c5c96bf93c23614e7b39142a8521fd02b7.yml openapi_spec_hash: c242c4c29807f5967f6ea1247e199dff -config_hash: a83108370c39c0ce1b66dbe018cdd484 +config_hash: ba820fe26ad022ecb70dcfe483706a7c diff --git a/api.md b/api.md index ffa40e63..c2cd84ae 100644 --- a/api.md +++ b/api.md @@ -340,7 +340,7 @@ Methods: Types: ```python -from whop_sdk.types import PaymentListResponse +from whop_sdk.types import BillingReasons, CardBrands, PaymentMethodTypes, PaymentListResponse ``` Methods: @@ -500,3 +500,16 @@ Methods: - client.course_lessons.update(id, \*\*params) -> Lesson - client.course_lessons.list(\*\*params) -> SyncCursorPage[CourseLessonListResponse] - client.course_lessons.delete(id) -> CourseLessonDeleteResponse + +# Reviews + +Types: + +```python +from whop_sdk.types import ReviewStatus, ReviewRetrieveResponse, ReviewListResponse +``` + +Methods: + +- client.reviews.retrieve(id) -> ReviewRetrieveResponse +- client.reviews.list(\*\*params) -> SyncCursorPage[ReviewListResponse] diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index a0182152..1ef88fa1 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -29,6 +29,7 @@ courses, entries, members, + reviews, invoices, messages, payments, @@ -93,6 +94,7 @@ class Whop(SyncAPIClient): courses: courses.CoursesResource course_chapters: course_chapters.CourseChaptersResource course_lessons: course_lessons.CourseLessonsResource + reviews: reviews.ReviewsResource with_raw_response: WhopWithRawResponse with_streaming_response: WhopWithStreamedResponse @@ -198,13 +200,14 @@ def __init__( self.courses = courses.CoursesResource(self) self.course_chapters = course_chapters.CourseChaptersResource(self) self.course_lessons = course_lessons.CourseLessonsResource(self) + self.reviews = reviews.ReviewsResource(self) self.with_raw_response = WhopWithRawResponse(self) self.with_streaming_response = WhopWithStreamedResponse(self) @property @override def qs(self) -> Querystring: - return Querystring(array_format="comma") + return Querystring(array_format="brackets") @property @override @@ -341,6 +344,7 @@ class AsyncWhop(AsyncAPIClient): courses: courses.AsyncCoursesResource course_chapters: course_chapters.AsyncCourseChaptersResource course_lessons: course_lessons.AsyncCourseLessonsResource + reviews: reviews.AsyncReviewsResource with_raw_response: AsyncWhopWithRawResponse with_streaming_response: AsyncWhopWithStreamedResponse @@ -446,13 +450,14 @@ def __init__( self.courses = courses.AsyncCoursesResource(self) self.course_chapters = course_chapters.AsyncCourseChaptersResource(self) self.course_lessons = course_lessons.AsyncCourseLessonsResource(self) + self.reviews = reviews.AsyncReviewsResource(self) self.with_raw_response = AsyncWhopWithRawResponse(self) self.with_streaming_response = AsyncWhopWithStreamedResponse(self) @property @override def qs(self) -> Querystring: - return Querystring(array_format="comma") + return Querystring(array_format="brackets") @property @override @@ -593,6 +598,7 @@ def __init__(self, client: Whop) -> None: self.courses = courses.CoursesResourceWithRawResponse(client.courses) self.course_chapters = course_chapters.CourseChaptersResourceWithRawResponse(client.course_chapters) self.course_lessons = course_lessons.CourseLessonsResourceWithRawResponse(client.course_lessons) + self.reviews = reviews.ReviewsResourceWithRawResponse(client.reviews) class AsyncWhopWithRawResponse: @@ -631,6 +637,7 @@ def __init__(self, client: AsyncWhop) -> None: self.courses = courses.AsyncCoursesResourceWithRawResponse(client.courses) self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithRawResponse(client.course_chapters) self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithRawResponse(client.course_lessons) + self.reviews = reviews.AsyncReviewsResourceWithRawResponse(client.reviews) class WhopWithStreamedResponse: @@ -669,6 +676,7 @@ def __init__(self, client: Whop) -> None: self.courses = courses.CoursesResourceWithStreamingResponse(client.courses) self.course_chapters = course_chapters.CourseChaptersResourceWithStreamingResponse(client.course_chapters) self.course_lessons = course_lessons.CourseLessonsResourceWithStreamingResponse(client.course_lessons) + self.reviews = reviews.ReviewsResourceWithStreamingResponse(client.reviews) class AsyncWhopWithStreamedResponse: @@ -711,6 +719,7 @@ def __init__(self, client: AsyncWhop) -> None: self.courses = courses.AsyncCoursesResourceWithStreamingResponse(client.courses) self.course_chapters = course_chapters.AsyncCourseChaptersResourceWithStreamingResponse(client.course_chapters) self.course_lessons = course_lessons.AsyncCourseLessonsResourceWithStreamingResponse(client.course_lessons) + self.reviews = reviews.AsyncReviewsResourceWithStreamingResponse(client.reviews) Client = Whop diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index 9c8fe350..e6d4acc8 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -56,6 +56,14 @@ MembersResourceWithStreamingResponse, AsyncMembersResourceWithStreamingResponse, ) +from .reviews import ( + ReviewsResource, + AsyncReviewsResource, + ReviewsResourceWithRawResponse, + AsyncReviewsResourceWithRawResponse, + ReviewsResourceWithStreamingResponse, + AsyncReviewsResourceWithStreamingResponse, +) from .invoices import ( InvoicesResource, AsyncInvoicesResource, @@ -397,4 +405,10 @@ "AsyncCourseLessonsResourceWithRawResponse", "CourseLessonsResourceWithStreamingResponse", "AsyncCourseLessonsResourceWithStreamingResponse", + "ReviewsResource", + "AsyncReviewsResource", + "ReviewsResourceWithRawResponse", + "AsyncReviewsResourceWithRawResponse", + "ReviewsResourceWithStreamingResponse", + "AsyncReviewsResourceWithStreamingResponse", ] diff --git a/src/whop_sdk/resources/payments.py b/src/whop_sdk/resources/payments.py index a1b5ddde..e486d203 100644 --- a/src/whop_sdk/resources/payments.py +++ b/src/whop_sdk/resources/payments.py @@ -22,6 +22,7 @@ from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options from ..types.shared.payment import Payment +from ..types.billing_reasons import BillingReasons from ..types.shared.currency import Currency from ..types.shared.direction import Direction from ..types.payment_list_response import PaymentListResponse @@ -100,19 +101,7 @@ def list( company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, - billing_reasons: Optional[ - List[ - Literal[ - "subscription_create", - "subscription_cycle", - "subscription_update", - "one_time", - "manual", - "subscription", - ] - ] - ] - | Omit = omit, + billing_reasons: Optional[List[BillingReasons]] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, currencies: Optional[List[Currency]] | Omit = omit, @@ -422,19 +411,7 @@ def list( company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, - billing_reasons: Optional[ - List[ - Literal[ - "subscription_create", - "subscription_cycle", - "subscription_update", - "one_time", - "manual", - "subscription", - ] - ] - ] - | Omit = omit, + billing_reasons: Optional[List[BillingReasons]] | Omit = omit, created_after: Union[str, datetime, None] | Omit = omit, created_before: Union[str, datetime, None] | Omit = omit, currencies: Optional[List[Currency]] | Omit = omit, diff --git a/src/whop_sdk/resources/reviews.py b/src/whop_sdk/resources/reviews.py new file mode 100644 index 00000000..4a6538e4 --- /dev/null +++ b/src/whop_sdk/resources/reviews.py @@ -0,0 +1,315 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..types import review_list_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.review_list_response import ReviewListResponse +from ..types.review_retrieve_response import ReviewRetrieveResponse + +__all__ = ["ReviewsResource", "AsyncReviewsResource"] + + +class ReviewsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ReviewsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return ReviewsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ReviewsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return ReviewsResourceWithStreamingResponse(self) + + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ReviewRetrieveResponse: + """ + Retrieve a review by its ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + f"/reviews/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ReviewRetrieveResponse, + ) + + def list( + self, + *, + product_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + max_stars: Optional[int] | Omit = omit, + min_stars: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ReviewListResponse]: + """ + List all reviews + + Args: + product_id: The ID of the product + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + max_stars: The maximum star rating of the review (inclusive) + + min_stars: The minimum star rating of the review (inclusive) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/reviews", + page=SyncCursorPage[ReviewListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "product_id": product_id, + "after": after, + "before": before, + "first": first, + "last": last, + "max_stars": max_stars, + "min_stars": min_stars, + }, + review_list_params.ReviewListParams, + ), + ), + model=ReviewListResponse, + ) + + +class AsyncReviewsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncReviewsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncReviewsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncReviewsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncReviewsResourceWithStreamingResponse(self) + + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ReviewRetrieveResponse: + """ + Retrieve a review by its ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + f"/reviews/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ReviewRetrieveResponse, + ) + + def list( + self, + *, + product_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + max_stars: Optional[int] | Omit = omit, + min_stars: Optional[int] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ReviewListResponse, AsyncCursorPage[ReviewListResponse]]: + """ + List all reviews + + Args: + product_id: The ID of the product + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + max_stars: The maximum star rating of the review (inclusive) + + min_stars: The minimum star rating of the review (inclusive) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/reviews", + page=AsyncCursorPage[ReviewListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "product_id": product_id, + "after": after, + "before": before, + "first": first, + "last": last, + "max_stars": max_stars, + "min_stars": min_stars, + }, + review_list_params.ReviewListParams, + ), + ), + model=ReviewListResponse, + ) + + +class ReviewsResourceWithRawResponse: + def __init__(self, reviews: ReviewsResource) -> None: + self._reviews = reviews + + self.retrieve = to_raw_response_wrapper( + reviews.retrieve, + ) + self.list = to_raw_response_wrapper( + reviews.list, + ) + + +class AsyncReviewsResourceWithRawResponse: + def __init__(self, reviews: AsyncReviewsResource) -> None: + self._reviews = reviews + + self.retrieve = async_to_raw_response_wrapper( + reviews.retrieve, + ) + self.list = async_to_raw_response_wrapper( + reviews.list, + ) + + +class ReviewsResourceWithStreamingResponse: + def __init__(self, reviews: ReviewsResource) -> None: + self._reviews = reviews + + self.retrieve = to_streamed_response_wrapper( + reviews.retrieve, + ) + self.list = to_streamed_response_wrapper( + reviews.list, + ) + + +class AsyncReviewsResourceWithStreamingResponse: + def __init__(self, reviews: AsyncReviewsResource) -> None: + self._reviews = reviews + + self.retrieve = async_to_streamed_response_wrapper( + reviews.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + reviews.list, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index 22d1d3bf..33bebb0e 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -68,10 +68,13 @@ ) from .languages import Languages as Languages from .promo_code import PromoCode as PromoCode +from .card_brands import CardBrands as CardBrands from .lesson_types import LessonTypes as LessonTypes +from .review_status import ReviewStatus as ReviewStatus from .course_chapter import CourseChapter as CourseChapter from .promo_duration import PromoDuration as PromoDuration from .app_list_params import AppListParams as AppListParams +from .billing_reasons import BillingReasons as BillingReasons from .plan_list_params import PlanListParams as PlanListParams from .app_create_params import AppCreateParams as AppCreateParams from .app_list_response import AppListResponse as AppListResponse @@ -84,6 +87,7 @@ from .plan_create_params import PlanCreateParams as PlanCreateParams from .plan_list_response import PlanListResponse as PlanListResponse from .plan_update_params import PlanUpdateParams as PlanUpdateParams +from .review_list_params import ReviewListParams as ReviewListParams from .entry_list_response import EntryListResponse as EntryListResponse from .forum_list_response import ForumListResponse as ForumListResponse from .forum_update_params import ForumUpdateParams as ForumUpdateParams @@ -96,8 +100,10 @@ from .course_list_response import CourseListResponse as CourseListResponse from .course_update_params import CourseUpdateParams as CourseUpdateParams from .member_list_response import MemberListResponse as MemberListResponse +from .payment_method_types import PaymentMethodTypes as PaymentMethodTypes from .plan_delete_response import PlanDeleteResponse as PlanDeleteResponse from .reaction_list_params import ReactionListParams as ReactionListParams +from .review_list_response import ReviewListResponse as ReviewListResponse from .shipment_list_params import ShipmentListParams as ShipmentListParams from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent @@ -144,6 +150,7 @@ from .membership_update_params import MembershipUpdateParams as MembershipUpdateParams from .promo_code_create_params import PromoCodeCreateParams as PromoCodeCreateParams from .promo_code_list_response import PromoCodeListResponse as PromoCodeListResponse +from .review_retrieve_response import ReviewRetrieveResponse as ReviewRetrieveResponse from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes from .course_lesson_list_params import CourseLessonListParams as CourseLessonListParams from .chat_channel_list_response import ChatChannelListResponse as ChatChannelListResponse diff --git a/src/whop_sdk/types/billing_reasons.py b/src/whop_sdk/types/billing_reasons.py new file mode 100644 index 00000000..a35732a1 --- /dev/null +++ b/src/whop_sdk/types/billing_reasons.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["BillingReasons"] + +BillingReasons: TypeAlias = Literal[ + "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" +] diff --git a/src/whop_sdk/types/card_brands.py b/src/whop_sdk/types/card_brands.py new file mode 100644 index 00000000..f500a411 --- /dev/null +++ b/src/whop_sdk/types/card_brands.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["CardBrands"] + +CardBrands: TypeAlias = Literal[ + "mastercard", + "visa", + "amex", + "discover", + "unionpay", + "jcb", + "diners", + "link", + "troy", + "visadankort", + "visabancontact", + "china_union_pay", + "rupay", + "jcbrupay", + "elo", + "unknown", +] diff --git a/src/whop_sdk/types/payment_list_params.py b/src/whop_sdk/types/payment_list_params.py index 861346af..583427ae 100644 --- a/src/whop_sdk/types/payment_list_params.py +++ b/src/whop_sdk/types/payment_list_params.py @@ -8,6 +8,7 @@ from .._types import SequenceNotStr from .._utils import PropertyInfo +from .billing_reasons import BillingReasons from .shared.currency import Currency from .shared.direction import Direction from .shared.receipt_status import ReceiptStatus @@ -26,13 +27,7 @@ class PaymentListParams(TypedDict, total=False): before: Optional[str] """Returns the elements in the list that come before the specified cursor.""" - billing_reasons: Optional[ - List[ - Literal[ - "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" - ] - ] - ] + billing_reasons: Optional[List[BillingReasons]] """The billing reason for the payment""" created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 04f46e9c..6ff597b0 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -2,11 +2,13 @@ from typing import Optional from datetime import datetime -from typing_extensions import Literal from .._models import BaseModel +from .card_brands import CardBrands +from .billing_reasons import BillingReasons from .shared.currency import Currency from .shared.promo_type import PromoType +from .payment_method_types import PaymentMethodTypes from .shared.receipt_status import ReceiptStatus from .shared.membership_status import MembershipStatus from .shared.friendly_receipt_status import FriendlyReceiptStatus @@ -137,33 +139,10 @@ class PaymentListResponse(BaseModel): billing_address: Optional[BillingAddress] = None """The address of the user who made the payment.""" - billing_reason: Optional[ - Literal[ - "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" - ] - ] = None + billing_reason: Optional[BillingReasons] = None """The reason why a specific payment was billed""" - card_brand: Optional[ - Literal[ - "mastercard", - "visa", - "amex", - "discover", - "unionpay", - "jcb", - "diners", - "link", - "troy", - "visadankort", - "visabancontact", - "china_union_pay", - "rupay", - "jcbrupay", - "elo", - "unknown", - ] - ] = None + card_brand: Optional[CardBrands] = None """Possible card brands that a payment token can have""" card_last4: Optional[str] = None @@ -196,94 +175,7 @@ class PaymentListResponse(BaseModel): paid_at: Optional[datetime] = None """The datetime the payment was paid""" - payment_method_type: Optional[ - Literal[ - "acss_debit", - "affirm", - "afterpay_clearpay", - "alipay", - "alma", - "amazon_pay", - "apple_pay", - "au_becs_debit", - "bacs_debit", - "bancontact", - "billie", - "blik", - "boleto", - "card", - "cashapp", - "crypto", - "eps", - "fpx", - "giropay", - "google_pay", - "grabpay", - "ideal", - "kakao_pay", - "klarna", - "konbini", - "kr_card", - "link", - "mobilepay", - "multibanco", - "naver_pay", - "nz_bank_account", - "oxxo", - "p24", - "pay_by_bank", - "payco", - "paynow", - "pix", - "promptpay", - "revolut_pay", - "samsung_pay", - "satispay", - "sepa_debit", - "sofort", - "swish", - "twint", - "us_bank_account", - "wechat_pay", - "zip", - "bizum", - "capchase_pay", - "kriya", - "mondu", - "ng_wallet", - "paypay", - "sequra", - "scalapay", - "vipps", - "custom", - "customer_balance", - "gopay", - "mb_way", - "ng_bank", - "ng_bank_transfer", - "ng_card", - "ng_market", - "ng_ussd", - "paypal", - "payto", - "qris", - "rechnung", - "south_korea_market", - "kr_market", - "shopeepay", - "upi", - "sunbit", - "netbanking", - "id_bank_transfer", - "demo_pay", - "shop_pay", - "apple", - "sezzle", - "coinbase", - "splitit", - "unknown", - ] - ] = None + payment_method_type: Optional[PaymentMethodTypes] = None """The different types of payment methods that can be used.""" plan: Optional[Plan] = None diff --git a/src/whop_sdk/types/payment_method_types.py b/src/whop_sdk/types/payment_method_types.py new file mode 100644 index 00000000..72c576f6 --- /dev/null +++ b/src/whop_sdk/types/payment_method_types.py @@ -0,0 +1,92 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["PaymentMethodTypes"] + +PaymentMethodTypes: TypeAlias = Literal[ + "acss_debit", + "affirm", + "afterpay_clearpay", + "alipay", + "alma", + "amazon_pay", + "apple_pay", + "au_becs_debit", + "bacs_debit", + "bancontact", + "billie", + "blik", + "boleto", + "card", + "cashapp", + "crypto", + "eps", + "fpx", + "giropay", + "google_pay", + "grabpay", + "ideal", + "kakao_pay", + "klarna", + "konbini", + "kr_card", + "link", + "mobilepay", + "multibanco", + "naver_pay", + "nz_bank_account", + "oxxo", + "p24", + "pay_by_bank", + "payco", + "paynow", + "pix", + "promptpay", + "revolut_pay", + "samsung_pay", + "satispay", + "sepa_debit", + "sofort", + "swish", + "twint", + "us_bank_account", + "wechat_pay", + "zip", + "bizum", + "capchase_pay", + "kriya", + "mondu", + "ng_wallet", + "paypay", + "sequra", + "scalapay", + "vipps", + "custom", + "customer_balance", + "gopay", + "mb_way", + "ng_bank", + "ng_bank_transfer", + "ng_card", + "ng_market", + "ng_ussd", + "paypal", + "payto", + "qris", + "rechnung", + "south_korea_market", + "kr_market", + "shopeepay", + "upi", + "sunbit", + "netbanking", + "id_bank_transfer", + "demo_pay", + "shop_pay", + "apple", + "sezzle", + "coinbase", + "splitit", + "unknown", +] diff --git a/src/whop_sdk/types/review_list_params.py b/src/whop_sdk/types/review_list_params.py new file mode 100644 index 00000000..25a19401 --- /dev/null +++ b/src/whop_sdk/types/review_list_params.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["ReviewListParams"] + + +class ReviewListParams(TypedDict, total=False): + product_id: Required[str] + """The ID of the product""" + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + max_stars: Optional[int] + """The maximum star rating of the review (inclusive)""" + + min_stars: Optional[int] + """The minimum star rating of the review (inclusive)""" diff --git a/src/whop_sdk/types/review_list_response.py b/src/whop_sdk/types/review_list_response.py new file mode 100644 index 00000000..5f463a80 --- /dev/null +++ b/src/whop_sdk/types/review_list_response.py @@ -0,0 +1,78 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from .._models import BaseModel +from .review_status import ReviewStatus + +__all__ = ["ReviewListResponse", "Attachment", "User"] + + +class Attachment(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + +class User(BaseModel): + id: str + """The internal ID of the user.""" + + name: Optional[str] = None + """The name of the user from their Whop account.""" + + username: str + """The username of the user from their Whop account.""" + + +class ReviewListResponse(BaseModel): + id: str + """The internal ID of the review.""" + + attachments: List[Attachment] + """The attachments attached to the review.""" + + created_at: datetime + """The timestamp of when the review was created.""" + + description: Optional[str] = None + """The description of the review.""" + + joined_at: Optional[datetime] = None + """The timestamp of when the user joined the product.""" + + paid_for_product: Optional[bool] = None + """Whether or not the user paid for the product. + + If null, the payment status is unknown. + """ + + published_at: Optional[datetime] = None + """The timestamp of when the review was published.""" + + stars: int + """The number of stars the user gave the product.""" + + status: ReviewStatus + """The status of the review.""" + + title: Optional[str] = None + """The title of the review.""" + + updated_at: datetime + """The timestamp of when the review was last updated.""" + + user: User + """The user account that performed the action.""" diff --git a/src/whop_sdk/types/review_retrieve_response.py b/src/whop_sdk/types/review_retrieve_response.py new file mode 100644 index 00000000..b71704fe --- /dev/null +++ b/src/whop_sdk/types/review_retrieve_response.py @@ -0,0 +1,103 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from .._models import BaseModel +from .review_status import ReviewStatus + +__all__ = ["ReviewRetrieveResponse", "Attachment", "Company", "Product", "User"] + + +class Attachment(BaseModel): + id: str + """The ID of the attachment""" + + content_type: Optional[str] = None + """The attachment's content type (e.g., image/jpg, video/mp4)""" + + filename: Optional[str] = None + """The name of the file""" + + url: Optional[str] = None + """This is the URL you use to render optimized attachments on the client. + + This should be used for apps. + """ + + +class Company(BaseModel): + id: str + """The ID (tag) of the company.""" + + route: str + """The slug/route of the company on the Whop site.""" + + title: str + """The title of the company.""" + + +class Product(BaseModel): + id: str + """The internal ID of the public product.""" + + title: str + """The title of the product. Use for Whop 4.0.""" + + +class User(BaseModel): + id: str + """The internal ID of the user.""" + + name: Optional[str] = None + """The name of the user from their Whop account.""" + + username: str + """The username of the user from their Whop account.""" + + +class ReviewRetrieveResponse(BaseModel): + id: str + """The internal ID of the review.""" + + attachments: List[Attachment] + """The attachments attached to the review.""" + + company: Company + """The company the review is for.""" + + created_at: datetime + """The timestamp of when the review was created.""" + + description: Optional[str] = None + """The description of the review.""" + + joined_at: Optional[datetime] = None + """The timestamp of when the user joined the product.""" + + paid_for_product: Optional[bool] = None + """Whether or not the user paid for the product. + + If null, the payment status is unknown. + """ + + product: Product + """The product the review is for.""" + + published_at: Optional[datetime] = None + """The timestamp of when the review was published.""" + + stars: int + """The number of stars the user gave the product.""" + + status: ReviewStatus + """The status of the review.""" + + title: Optional[str] = None + """The title of the review.""" + + updated_at: datetime + """The timestamp of when the review was last updated.""" + + user: User + """The user account that performed the action.""" diff --git a/src/whop_sdk/types/review_status.py b/src/whop_sdk/types/review_status.py new file mode 100644 index 00000000..fb262e40 --- /dev/null +++ b/src/whop_sdk/types/review_status.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal, TypeAlias + +__all__ = ["ReviewStatus"] + +ReviewStatus: TypeAlias = Literal["pending", "published", "removed"] diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 7a042e7e..73e60912 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -2,13 +2,15 @@ from typing import Optional from datetime import datetime -from typing_extensions import Literal from .currency import Currency from ..._models import BaseModel from .promo_type import PromoType +from ..card_brands import CardBrands from .receipt_status import ReceiptStatus +from ..billing_reasons import BillingReasons from .membership_status import MembershipStatus +from ..payment_method_types import PaymentMethodTypes from .friendly_receipt_status import FriendlyReceiptStatus __all__ = ["Payment", "BillingAddress", "Company", "Member", "Membership", "Plan", "Product", "PromoCode", "User"] @@ -127,33 +129,10 @@ class Payment(BaseModel): billing_address: Optional[BillingAddress] = None """The address of the user who made the payment.""" - billing_reason: Optional[ - Literal[ - "subscription_create", "subscription_cycle", "subscription_update", "one_time", "manual", "subscription" - ] - ] = None + billing_reason: Optional[BillingReasons] = None """The reason why a specific payment was billed""" - card_brand: Optional[ - Literal[ - "mastercard", - "visa", - "amex", - "discover", - "unionpay", - "jcb", - "diners", - "link", - "troy", - "visadankort", - "visabancontact", - "china_union_pay", - "rupay", - "jcbrupay", - "elo", - "unknown", - ] - ] = None + card_brand: Optional[CardBrands] = None """Possible card brands that a payment token can have""" card_last4: Optional[str] = None @@ -186,94 +165,7 @@ class Payment(BaseModel): paid_at: Optional[datetime] = None """The datetime the payment was paid""" - payment_method_type: Optional[ - Literal[ - "acss_debit", - "affirm", - "afterpay_clearpay", - "alipay", - "alma", - "amazon_pay", - "apple_pay", - "au_becs_debit", - "bacs_debit", - "bancontact", - "billie", - "blik", - "boleto", - "card", - "cashapp", - "crypto", - "eps", - "fpx", - "giropay", - "google_pay", - "grabpay", - "ideal", - "kakao_pay", - "klarna", - "konbini", - "kr_card", - "link", - "mobilepay", - "multibanco", - "naver_pay", - "nz_bank_account", - "oxxo", - "p24", - "pay_by_bank", - "payco", - "paynow", - "pix", - "promptpay", - "revolut_pay", - "samsung_pay", - "satispay", - "sepa_debit", - "sofort", - "swish", - "twint", - "us_bank_account", - "wechat_pay", - "zip", - "bizum", - "capchase_pay", - "kriya", - "mondu", - "ng_wallet", - "paypay", - "sequra", - "scalapay", - "vipps", - "custom", - "customer_balance", - "gopay", - "mb_way", - "ng_bank", - "ng_bank_transfer", - "ng_card", - "ng_market", - "ng_ussd", - "paypal", - "payto", - "qris", - "rechnung", - "south_korea_market", - "kr_market", - "shopeepay", - "upi", - "sunbit", - "netbanking", - "id_bank_transfer", - "demo_pay", - "shop_pay", - "apple", - "sezzle", - "coinbase", - "splitit", - "unknown", - ] - ] = None + payment_method_type: Optional[PaymentMethodTypes] = None """The different types of payment methods that can be used.""" plan: Optional[Plan] = None diff --git a/tests/api_resources/test_reviews.py b/tests/api_resources/test_reviews.py new file mode 100644 index 00000000..640ab0cd --- /dev/null +++ b/tests/api_resources/test_reviews.py @@ -0,0 +1,205 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ReviewListResponse, ReviewRetrieveResponse +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestReviews: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: Whop) -> None: + review = client.reviews.retrieve( + "rev_xxxxxxxxxxxxxx", + ) + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.reviews.with_raw_response.retrieve( + "rev_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + review = response.parse() + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.reviews.with_streaming_response.retrieve( + "rev_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + review = response.parse() + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.reviews.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + review = client.reviews.list( + product_id="prod_xxxxxxxxxxxxx", + ) + assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + review = client.reviews.list( + product_id="prod_xxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + max_stars=42, + min_stars=42, + ) + assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.reviews.with_raw_response.list( + product_id="prod_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + review = response.parse() + assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.reviews.with_streaming_response.list( + product_id="prod_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + review = response.parse() + assert_matches_type(SyncCursorPage[ReviewListResponse], review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncReviews: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWhop) -> None: + review = await async_client.reviews.retrieve( + "rev_xxxxxxxxxxxxxx", + ) + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.reviews.with_raw_response.retrieve( + "rev_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + review = await response.parse() + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.reviews.with_streaming_response.retrieve( + "rev_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + review = await response.parse() + assert_matches_type(ReviewRetrieveResponse, review, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.reviews.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + review = await async_client.reviews.list( + product_id="prod_xxxxxxxxxxxxx", + ) + assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + review = await async_client.reviews.list( + product_id="prod_xxxxxxxxxxxxx", + after="after", + before="before", + first=42, + last=42, + max_stars=42, + min_stars=42, + ) + assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.reviews.with_raw_response.list( + product_id="prod_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + review = await response.parse() + assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.reviews.with_streaming_response.list( + product_id="prod_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + review = await response.parse() + assert_matches_type(AsyncCursorPage[ReviewListResponse], review, path=["response"]) + + assert cast(Any, response.is_closed) is True From 40dae9169410ce124b1b98c960533b12bff84011 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 22:47:35 +0000 Subject: [PATCH 20/20] release: 0.0.3 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- src/whop_sdk/_version.py | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c7159c1a..28e831b6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.2" + ".": "0.0.3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c855ac..5fecd652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 0.0.3 (2025-10-28) + +Full Changelog: [v0.0.2...v0.0.3](https://github.com/whopio/whopsdk-python/compare/v0.0.2...v0.0.3) + +### Features + +* **api:** api update ([cb26567](https://github.com/whopio/whopsdk-python/commit/cb26567d0790a6b14a82a7b031e6319bc467af03)) +* **api:** api update ([9d1c491](https://github.com/whopio/whopsdk-python/commit/9d1c491cc58672b4d7bb54d14717704b5dda8d73)) +* **api:** api update ([fb03516](https://github.com/whopio/whopsdk-python/commit/fb03516f3ca1c79848e16fbf21f780fb76db226d)) +* **api:** api update ([e4d5e22](https://github.com/whopio/whopsdk-python/commit/e4d5e222ffc757b3255624c5824abcdc8312ea37)) +* **api:** api update ([88d7744](https://github.com/whopio/whopsdk-python/commit/88d7744347e459d509142bf983770cb5086b06c9)) +* **api:** api update ([853834f](https://github.com/whopio/whopsdk-python/commit/853834f283692e15c9539869b157fb42244ed8b0)) +* **api:** api update ([b8cb3de](https://github.com/whopio/whopsdk-python/commit/b8cb3de04d4122a4054a1f56cfee7637a0a1d0af)) +* **api:** api update ([06fcd76](https://github.com/whopio/whopsdk-python/commit/06fcd76e5f7c84b1d6a2dda07464867261ea9b58)) +* **api:** api update ([8708559](https://github.com/whopio/whopsdk-python/commit/87085590a3ce6debdf321045f88b99ee3625ca48)) +* **api:** api update ([bf6a189](https://github.com/whopio/whopsdk-python/commit/bf6a18904aaf4291f76e665be08fc39aff2f3731)) +* **api:** api update ([4d649cf](https://github.com/whopio/whopsdk-python/commit/4d649cf643820cbe60a4847c5eacba760d3b90ef)) +* **api:** api update ([986b31c](https://github.com/whopio/whopsdk-python/commit/986b31c8e8ddc6f621c8e737eeb5ba24be4ea648)) +* **api:** api update ([b7f500d](https://github.com/whopio/whopsdk-python/commit/b7f500dcaa5daa83d6d236e216f130fc6c4db29e)) +* **api:** api update ([e27cde0](https://github.com/whopio/whopsdk-python/commit/e27cde011cdda82c57f242acdd20ac7955619373)) +* **api:** manual updates ([981c0df](https://github.com/whopio/whopsdk-python/commit/981c0dfb7441c2322c75e2dd6620b5064dd294ec)) +* **api:** manual updates ([c41adf9](https://github.com/whopio/whopsdk-python/commit/c41adf99358901411a5947d09a691ab0f1d83f99)) + ## 0.0.2 (2025-10-22) Full Changelog: [v0.0.1...v0.0.2](https://github.com/whopio/whopsdk-python/compare/v0.0.1...v0.0.2) diff --git a/pyproject.toml b/pyproject.toml index 1af33a1c..954e784c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "whop-sdk" -version = "0.0.2" +version = "0.0.3" description = "The official Python library for the Whop API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/whop_sdk/_version.py b/src/whop_sdk/_version.py index d3607fd9..a12e2cff 100644 --- a/src/whop_sdk/_version.py +++ b/src/whop_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "whop_sdk" -__version__ = "0.0.2" # x-release-please-version +__version__ = "0.0.3" # x-release-please-version