diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1eb2a61..2ff85815 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,18 @@ name: CI on: push: - branches: - - main - pull_request: - branches: - - main - - next + branches-ignore: + - 'generated' + - 'codegen/**' + - 'integrated/**' + - 'preview-head/**' + - 'preview-base/**' + - 'preview/**' jobs: lint: name: lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -33,7 +33,6 @@ jobs: test: name: test runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6af24e3d..f2863752 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.89.0" + ".": "0.90.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 95273778..0b86f92e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.90.0 (2025-04-09) + +Full Changelog: [v0.89.0...v0.90.0](https://github.com/lithic-com/lithic-python/compare/v0.89.0...v0.90.0) + +### Features + +* **api:** manual updates ([6800098](https://github.com/lithic-com/lithic-python/commit/6800098f32ba1d88183219c9cc822a6e57670632)) + + +### Chores + +* configure new SDK language ([4a3c8b4](https://github.com/lithic-com/lithic-python/commit/4a3c8b431da736f617049f3e6b989a14635c09c6)) +* **internal:** expand CI branch coverage ([#736](https://github.com/lithic-com/lithic-python/issues/736)) ([01cf49e](https://github.com/lithic-com/lithic-python/commit/01cf49e2ae2fb20a52ce88d21368cd05b2b48dc6)) +* **tests:** improve enum examples ([#734](https://github.com/lithic-com/lithic-python/issues/734)) ([cd17f0d](https://github.com/lithic-com/lithic-python/commit/cd17f0d1a1152b464fba5d96f368289e14fe1a41)) + ## 0.89.0 (2025-04-08) Full Changelog: [v0.88.0...v0.89.0](https://github.com/lithic-com/lithic-python/compare/v0.88.0...v0.89.0) diff --git a/README.md b/README.md index 1a695287..95c3d2ec 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ from lithic import Lithic client = Lithic() card = client.cards.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) print(card.product_id) ``` diff --git a/pyproject.toml b/pyproject.toml index 9fb31ec0..7318e109 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lithic" -version = "0.89.0" +version = "0.90.0" description = "The official Python library for the lithic API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/lithic/_utils/_transform.py b/src/lithic/_utils/_transform.py index 7ac2e17f..3ec62081 100644 --- a/src/lithic/_utils/_transform.py +++ b/src/lithic/_utils/_transform.py @@ -142,6 +142,10 @@ def _maybe_transform_key(key: str, type_: type) -> str: return key +def _no_transform_needed(annotation: type) -> bool: + return annotation == float or annotation == int + + def _transform_recursive( data: object, *, @@ -184,6 +188,15 @@ def _transform_recursive( return cast(object, data) inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): @@ -332,6 +345,15 @@ async def _async_transform_recursive( return cast(object, data) inner_type = extract_type_arg(stripped_type, 0) + if _no_transform_needed(inner_type): + # for some types there is no need to transform anything, so we can get a small + # perf boost from skipping that work. + # + # but we still need to convert to a list to ensure the data is json-serializable + if is_list(data): + return data + return list(data) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] if is_union_type(stripped_type): diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 1307f0dd..059797fb 100644 --- a/src/lithic/_version.py +++ b/src/lithic/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "lithic" -__version__ = "0.89.0" # x-release-please-version +__version__ = "0.90.0" # x-release-please-version diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index 095e72a7..dd116a09 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -26,14 +26,14 @@ class TestCards: @parametrize def test_method_create(self, client: Lithic) -> None: card = client.cards.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) assert_matches_type(Card, card, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Lithic) -> None: card = client.cards.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_program_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", carrier={"qr_code_url": "qr_code_url"}, @@ -60,7 +60,7 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: }, shipping_method="2_DAY", spend_limit=1000, - spend_limit_duration="ANNUALLY", + spend_limit_duration="TRANSACTION", state="OPEN", ) assert_matches_type(Card, card, path=["response"]) @@ -68,7 +68,7 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: @parametrize def test_raw_response_create(self, client: Lithic) -> None: response = client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) assert response.is_closed is True @@ -79,7 +79,7 @@ def test_raw_response_create(self, client: Lithic) -> None: @parametrize def test_streaming_response_create(self, client: Lithic) -> None: with client.cards.with_streaming_response.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -143,8 +143,8 @@ def test_method_update_with_all_params(self, client: Lithic) -> None: pin="pin", pin_status="OK", spend_limit=100, - spend_limit_duration="ANNUALLY", - state="CLOSED", + spend_limit_duration="FOREVER", + state="OPEN", ) assert_matches_type(Card, card, path=["response"]) @@ -252,7 +252,7 @@ def test_method_convert_physical_with_all_params(self, client: Lithic) -> None: }, carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, product_id="100", - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) @@ -374,7 +374,7 @@ def test_method_provision_with_all_params(self, client: Lithic) -> None: certificate="U3RhaW5sZXNzIHJvY2tz", client_device_id="client_device_id", client_wallet_account_id="client_wallet_account_id", - digital_wallet="APPLE_PAY", + digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", ) @@ -437,7 +437,7 @@ def test_method_reissue_with_all_params(self, client: Lithic) -> None: "line2_text": "The Bluth Company", "phone_number": "+15555555555", }, - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) @@ -509,7 +509,7 @@ def test_method_renew_with_all_params(self, client: Lithic) -> None: exp_month="06", exp_year="2027", product_id="100", - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) @@ -647,14 +647,14 @@ class TestAsyncCards: @parametrize async def test_method_create(self, async_client: AsyncLithic) -> None: card = await async_client.cards.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) assert_matches_type(Card, card, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: card = await async_client.cards.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", card_program_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", carrier={"qr_code_url": "qr_code_url"}, @@ -681,7 +681,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> }, shipping_method="2_DAY", spend_limit=1000, - spend_limit_duration="ANNUALLY", + spend_limit_duration="TRANSACTION", state="OPEN", ) assert_matches_type(Card, card, path=["response"]) @@ -689,7 +689,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> @parametrize async def test_raw_response_create(self, async_client: AsyncLithic) -> None: response = await async_client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) assert response.is_closed is True @@ -700,7 +700,7 @@ async def test_raw_response_create(self, async_client: AsyncLithic) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: async with async_client.cards.with_streaming_response.create( - type="MERCHANT_LOCKED", + type="VIRTUAL", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -764,8 +764,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncLithic) -> pin="pin", pin_status="OK", spend_limit=100, - spend_limit_duration="ANNUALLY", - state="CLOSED", + spend_limit_duration="FOREVER", + state="OPEN", ) assert_matches_type(Card, card, path=["response"]) @@ -873,7 +873,7 @@ async def test_method_convert_physical_with_all_params(self, async_client: Async }, carrier={"qr_code_url": "https://lithic.com/activate-card/1"}, product_id="100", - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) @@ -995,7 +995,7 @@ async def test_method_provision_with_all_params(self, async_client: AsyncLithic) certificate="U3RhaW5sZXNzIHJvY2tz", client_device_id="client_device_id", client_wallet_account_id="client_wallet_account_id", - digital_wallet="APPLE_PAY", + digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", ) @@ -1058,7 +1058,7 @@ async def test_method_reissue_with_all_params(self, async_client: AsyncLithic) - "line2_text": "The Bluth Company", "phone_number": "+15555555555", }, - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) @@ -1130,7 +1130,7 @@ async def test_method_renew_with_all_params(self, async_client: AsyncLithic) -> exp_month="06", exp_year="2027", product_id="100", - shipping_method="2_DAY", + shipping_method="STANDARD", ) assert_matches_type(Card, card, path=["response"]) diff --git a/tests/api_resources/test_disputes.py b/tests/api_resources/test_disputes.py index 2104bf81..01f9e422 100644 --- a/tests/api_resources/test_disputes.py +++ b/tests/api_resources/test_disputes.py @@ -26,7 +26,7 @@ class TestDisputes: def test_method_create(self, client: Lithic) -> None: dispute = client.disputes.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -35,7 +35,7 @@ def test_method_create(self, client: Lithic) -> None: def test_method_create_with_all_params(self, client: Lithic) -> None: dispute = client.disputes.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", customer_filed_date=parse_datetime("2021-06-28T22:53:15Z"), customer_note="customer_note", @@ -46,7 +46,7 @@ def test_method_create_with_all_params(self, client: Lithic) -> None: def test_raw_response_create(self, client: Lithic) -> None: response = client.disputes.with_raw_response.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) @@ -59,7 +59,7 @@ def test_raw_response_create(self, client: Lithic) -> None: def test_streaming_response_create(self, client: Lithic) -> None: with client.disputes.with_streaming_response.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) as response: assert not response.is_closed @@ -433,7 +433,7 @@ class TestAsyncDisputes: async def test_method_create(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) assert_matches_type(Dispute, dispute, path=["response"]) @@ -442,7 +442,7 @@ async def test_method_create(self, async_client: AsyncLithic) -> None: async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> None: dispute = await async_client.disputes.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", customer_filed_date=parse_datetime("2021-06-28T22:53:15Z"), customer_note="customer_note", @@ -453,7 +453,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLithic) -> async def test_raw_response_create(self, async_client: AsyncLithic) -> None: response = await async_client.disputes.with_raw_response.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) @@ -466,7 +466,7 @@ async def test_raw_response_create(self, async_client: AsyncLithic) -> None: async def test_streaming_response_create(self, async_client: AsyncLithic) -> None: async with async_client.disputes.with_streaming_response.create( amount=10000, - reason="ATM_CASH_MISDISPENSE", + reason="FRAUD_CARD_PRESENT", transaction_token="12345624-aa69-4cbc-a946-30d90181b621", ) as response: assert not response.is_closed diff --git a/tests/api_resources/test_tokenizations.py b/tests/api_resources/test_tokenizations.py index 3fecca6a..cb3402ac 100644 --- a/tests/api_resources/test_tokenizations.py +++ b/tests/api_resources/test_tokenizations.py @@ -226,7 +226,7 @@ def test_method_resend_activation_code(self, client: Lithic) -> None: def test_method_resend_activation_code_with_all_params(self, client: Lithic) -> None: tokenization = client.tokenizations.resend_activation_code( tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - activation_method_type="EMAIL_TO_CARDHOLDER_ADDRESS", + activation_method_type="TEXT_TO_CARDHOLDER_NUMBER", ) assert tokenization is None @@ -605,7 +605,7 @@ async def test_method_resend_activation_code(self, async_client: AsyncLithic) -> async def test_method_resend_activation_code_with_all_params(self, async_client: AsyncLithic) -> None: tokenization = await async_client.tokenizations.resend_activation_code( tokenization_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - activation_method_type="EMAIL_TO_CARDHOLDER_ADDRESS", + activation_method_type="TEXT_TO_CARDHOLDER_NUMBER", ) assert tokenization is None diff --git a/tests/test_client.py b/tests/test_client.py index db2c23c4..2ab10ad2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -768,7 +768,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) - response = client.cards.with_raw_response.create(type="MERCHANT_LOCKED") + response = client.cards.with_raw_response.create(type="VIRTUAL") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -793,7 +793,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) response = client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", extra_headers={"x-stainless-retry-count": Omit()} + type="VIRTUAL", extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -818,7 +818,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) response = client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", extra_headers={"x-stainless-retry-count": "42"} + type="VIRTUAL", extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -842,7 +842,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) - with client.cards.with_streaming_response.create(type="MERCHANT_LOCKED") as response: + with client.cards.with_streaming_response.create(type="VIRTUAL") as response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1582,7 +1582,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) - response = await client.cards.with_raw_response.create(type="MERCHANT_LOCKED") + response = await client.cards.with_raw_response.create(type="VIRTUAL") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1608,7 +1608,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) response = await client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", extra_headers={"x-stainless-retry-count": Omit()} + type="VIRTUAL", extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1634,7 +1634,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) response = await client.cards.with_raw_response.create( - type="MERCHANT_LOCKED", extra_headers={"x-stainless-retry-count": "42"} + type="VIRTUAL", extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1659,7 +1659,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/cards").mock(side_effect=retry_handler) - async with client.cards.with_streaming_response.create(type="MERCHANT_LOCKED") as response: + async with client.cards.with_streaming_response.create(type="VIRTUAL") as response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success diff --git a/tests/test_transform.py b/tests/test_transform.py index d92ec53e..7ce3274b 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -432,3 +432,15 @@ async def test_base64_file_input(use_async: bool) -> None: assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { "foo": "SGVsbG8sIHdvcmxkIQ==" } # type: ignore[comparison-overlap] + + +@parametrize +@pytest.mark.asyncio +async def test_transform_skipping(use_async: bool) -> None: + # lists of ints are left as-is + data = [1, 2, 3] + assert await transform(data, List[int], use_async) is data + + # iterables of ints are converted to a list + data = iter([1, 2, 3]) + assert await transform(data, Iterable[int], use_async) == [1, 2, 3]