Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.72.0"
".": "1.73.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 220
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/conductor%2Fconductor-235b86151cc83acadb1123adb78fd2b383f97a96daa1b19f36d5be77f546fce0.yml
openapi_spec_hash: fb2c7bfec6572f694f9a2f53e44e67a8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/conductor%2Fconductor-c92c78f67f08c9b7c4d6925584c54690ae2381d4d5033cecff3a47e68ff1eb08.yml
openapi_spec_hash: cdd305329a64f2c49890863045f79e66
config_hash: 89303a38c78d93021ba8a584462375bd
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 1.73.0 (2026-02-10)

Full Changelog: [v1.72.0...v1.73.0](https://github.com/conductor-is/quickbooks-desktop-python/compare/v1.72.0...v1.73.0)

### Features

* **api:** api update ([7009ef2](https://github.com/conductor-is/quickbooks-desktop-python/commit/7009ef21ef4db613c13ba51adac12e23b912b1c7))
* **client:** add custom JSON encoder for extended type support ([4626598](https://github.com/conductor-is/quickbooks-desktop-python/commit/462659881f36232051e2d3b247df04dbdb57e5d0))


### Bug Fixes

* **docs:** fix mcp installation instructions for remote servers ([d6dbf78](https://github.com/conductor-is/quickbooks-desktop-python/commit/d6dbf78b49fe2730b3c44f0c0bc032be5f2d900f))


### Chores

* **internal:** bump dependencies ([210256e](https://github.com/conductor-is/quickbooks-desktop-python/commit/210256e7a330aad0685ab0adfb441f958b00027e))

## 1.72.0 (2026-01-27)

Full Changelog: [v1.71.1...v1.72.0](https://github.com/conductor-is/quickbooks-desktop-python/compare/v1.71.1...v1.72.0)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ The Conductor **Python** library provides convenient access to our QuickBooks De

Use the Conductor MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.

[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=conductor-node-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNvbmR1Y3Rvci1ub2RlLW1jcCJdfQ)
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22conductor-node-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22conductor-node-mcp%22%5D%7D)
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=conductor-node-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNvbmR1Y3Rvci1ub2RlLW1jcCJdLCJlbnYiOnsiQ09ORFVDVE9SX1NFQ1JFVF9LRVkiOiJza19jb25kdWN0b3JfLi4uIn19)
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22conductor-node-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22conductor-node-mcp%22%5D%2C%22env%22%3A%7B%22CONDUCTOR_SECRET_KEY%22%3A%22sk_conductor_...%22%7D%7D)

> Note: You may need to set environment variables in your MCP client.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "conductor-py"
version = "1.72.0"
version = "1.73.0"
description = "The official Python library for the conductor API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
20 changes: 10 additions & 10 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.13.2
aiohttp==3.13.3
# via conductor-py
# via httpx-aiohttp
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
anyio==4.12.0
anyio==4.12.1
# via conductor-py
# via httpx
argcomplete==3.6.3
Expand All @@ -31,7 +31,7 @@ attrs==25.4.0
# via nox
backports-asyncio-runner==1.2.0
# via pytest-asyncio
certifi==2025.11.12
certifi==2026.1.4
# via httpcore
# via httpx
colorlog==6.10.1
Expand Down Expand Up @@ -61,15 +61,15 @@ httpx==0.28.1
# via conductor-py
# via httpx-aiohttp
# via respx
httpx-aiohttp==0.1.9
httpx-aiohttp==0.1.12
# via conductor-py
humanize==4.13.0
# via nox
idna==3.11
# via anyio
# via httpx
# via yarl
importlib-metadata==8.7.0
importlib-metadata==8.7.1
iniconfig==2.1.0
# via pytest
markdown-it-py==3.0.0
Expand All @@ -82,14 +82,14 @@ multidict==6.7.0
mypy==1.17.0
mypy-extensions==1.1.0
# via mypy
nodeenv==1.9.1
nodeenv==1.10.0
# via pyright
nox==2025.11.12
packaging==25.0
# via dependency-groups
# via nox
# via pytest
pathspec==0.12.1
pathspec==1.0.3
# via mypy
platformdirs==4.4.0
# via virtualenv
Expand All @@ -115,13 +115,13 @@ python-dateutil==2.9.0.post0
# via time-machine
respx==0.22.0
rich==14.2.0
ruff==0.14.7
ruff==0.14.13
six==1.17.0
# via python-dateutil
sniffio==1.3.1
# via conductor-py
time-machine==2.19.0
tomli==2.3.0
tomli==2.4.0
# via dependency-groups
# via mypy
# via nox
Expand All @@ -141,7 +141,7 @@ typing-extensions==4.15.0
# via virtualenv
typing-inspection==0.4.2
# via pydantic
virtualenv==20.35.4
virtualenv==20.36.1
# via nox
yarl==1.22.0
# via aiohttp
Expand Down
8 changes: 4 additions & 4 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.13.2
aiohttp==3.13.3
# via conductor-py
# via httpx-aiohttp
aiosignal==1.4.0
# via aiohttp
annotated-types==0.7.0
# via pydantic
anyio==4.12.0
anyio==4.12.1
# via conductor-py
# via httpx
async-timeout==5.0.1
# via aiohttp
attrs==25.4.0
# via aiohttp
certifi==2025.11.12
certifi==2026.1.4
# via httpcore
# via httpx
distro==1.9.0
Expand All @@ -43,7 +43,7 @@ httpcore==1.0.9
httpx==0.28.1
# via conductor-py
# via httpx-aiohttp
httpx-aiohttp==0.1.9
httpx-aiohttp==0.1.12
# via conductor-py
idna==3.11
# via anyio
Expand Down
7 changes: 5 additions & 2 deletions src/conductor/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
from ._utils._json import openapi_dumps

log: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
elif not files:
# Don't set content when JSON is sent as multipart/form-data,
# since httpx's content param overrides other body arguments
kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
Expand Down
6 changes: 3 additions & 3 deletions src/conductor/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
Expand All @@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
exclude=exclude,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)

Expand Down
35 changes: 35 additions & 0 deletions src/conductor/_utils/_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
"""
Serialize an object to UTF-8 encoded JSON bytes.

Extends the standard json.dumps with support for additional types
commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
"""
return json.dumps(
obj,
cls=_CustomEncoder,
# Uses the same defaults as httpx's JSON serialization
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
).encode()


class _CustomEncoder(json.JSONEncoder):
@override
def default(self, o: Any) -> Any:
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, pydantic.BaseModel):
return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
return super().default(o)
2 changes: 1 addition & 1 deletion src/conductor/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "conductor"
__version__ = "1.72.0" # x-release-please-version
__version__ = "1.73.0" # x-release-please-version
2 changes: 1 addition & 1 deletion src/conductor/types/qbd/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ class SalesTax(BaseModel):
If sales tax is turned off in the user interface (that is, if "No" is selected for "Do You Charge Sales Tax?" in the sales tax preferences), then this field will be `null`.
"""

default_item_sales_tax: SalesTaxDefaultItemSalesTax = FieldInfo(alias="defaultItemSalesTax")
default_item_sales_tax: Optional[SalesTaxDefaultItemSalesTax] = FieldInfo(alias="defaultItemSalesTax", default=None)
"""The default tax code for sales for this company file."""

default_non_taxable_sales_tax_code: SalesTaxDefaultNonTaxableSalesTaxCode = FieldInfo(
Expand Down
126 changes: 126 additions & 0 deletions tests/test_utils/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from __future__ import annotations

import datetime
from typing import Union

import pydantic

from conductor import _compat
from conductor._utils._json import openapi_dumps


class TestOpenapiDumps:
def test_basic(self) -> None:
data = {"key": "value", "number": 42}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"key":"value","number":42}'

def test_datetime_serialization(self) -> None:
dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
data = {"datetime": dt}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'

def test_pydantic_model_serialization(self) -> None:
class User(pydantic.BaseModel):
first_name: str
last_name: str
age: int

model_instance = User(first_name="John", last_name="Kramer", age=83)
data = {"model": model_instance}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'

def test_pydantic_model_with_default_values(self) -> None:
class User(pydantic.BaseModel):
name: str
role: str = "user"
active: bool = True
score: int = 0

model_instance = User(name="Alice")
data = {"model": model_instance}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"name":"Alice"}}'

def test_pydantic_model_with_default_values_overridden(self) -> None:
class User(pydantic.BaseModel):
name: str
role: str = "user"
active: bool = True

model_instance = User(name="Bob", role="admin", active=False)
data = {"model": model_instance}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'

def test_pydantic_model_with_alias(self) -> None:
class User(pydantic.BaseModel):
first_name: str = pydantic.Field(alias="firstName")
last_name: str = pydantic.Field(alias="lastName")

model_instance = User(firstName="John", lastName="Doe")
data = {"model": model_instance}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'

def test_pydantic_model_with_alias_and_default(self) -> None:
class User(pydantic.BaseModel):
user_name: str = pydantic.Field(alias="userName")
user_role: str = pydantic.Field(default="member", alias="userRole")
is_active: bool = pydantic.Field(default=True, alias="isActive")

model_instance = User(userName="charlie")
data = {"model": model_instance}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"userName":"charlie"}}'

model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
data = {"model": model_with_overrides}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'

def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
class Address(pydantic.BaseModel):
street: str
city: str = "Unknown"

class User(pydantic.BaseModel):
name: str
address: Address
verified: bool = False

if _compat.PYDANTIC_V1:
# to handle forward references in Pydantic v1
User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]

address = Address(street="123 Main St")
user = User(name="Diana", address=address)
data = {"user": user}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'

address_with_city = Address(street="456 Oak Ave", city="Boston")
user_verified = User(name="Eve", address=address_with_city, verified=True)
data = {"user": user_verified}
json_bytes = openapi_dumps(data)
assert (
json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
)

def test_pydantic_model_with_optional_fields(self) -> None:
class User(pydantic.BaseModel):
name: str
email: Union[str, None]
phone: Union[str, None]

model_with_none = User(name="Eve", email=None, phone=None)
data = {"model": model_with_none}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'

model_with_values = User(name="Frank", email="frank@example.com", phone=None)
data = {"model": model_with_values}
json_bytes = openapi_dumps(data)
assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'