Skip to content

Commit 618d634

Browse files
committed
add orm level update() for targeted data changes
1 parent 4ed96d3 commit 618d634

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

src/bubble_data_api_client/client/orm.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from bubble_data_api_client.client.raw_client import RawClient
99
from bubble_data_api_client.constraints import Constraint, ConstraintType, constraint
10+
from bubble_data_api_client.exceptions import UnknownFieldError
1011
from bubble_data_api_client.types import BubbleField, OnMultiple
1112

1213

@@ -60,6 +61,23 @@ async def save(self) -> None:
6061
response = await client.update(self._typename, self.uid, data)
6162
response.raise_for_status()
6263

64+
@classmethod
65+
async def update(cls, uid: str, **data: typing.Any) -> None:
66+
"""Update specific fields on a thing by its unique ID."""
67+
aliased_data: dict[str, typing.Any] = {}
68+
for field_name, value in data.items():
69+
field_info = cls.model_fields.get(field_name)
70+
if field_info is None:
71+
raise UnknownFieldError(field_name)
72+
if field_info.alias:
73+
aliased_data[field_info.alias] = value
74+
else:
75+
aliased_data[field_name] = value
76+
77+
async with _get_client() as client:
78+
response = await client.update(cls._typename, uid, aliased_data)
79+
response.raise_for_status()
80+
6381
async def delete(self) -> None:
6482
async with _get_client() as client:
6583
response = await client.delete(self._typename, self.uid)

src/bubble_data_api_client/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ def __init__(self, value: str) -> None:
2525
self.value = value
2626

2727

28+
class UnknownFieldError(BubbleError):
29+
"""Raised when an unknown field name is passed to update()."""
30+
31+
def __init__(self, field_name: str) -> None:
32+
super().__init__(f"unknown field: {field_name}")
33+
self.field_name = field_name
34+
35+
2836
class MultipleMatchesError(BubbleError):
2937
"""Raised when create_or_update finds multiple matches with on_multiple='error'."""
3038

src/tests/unit/client/test_orm.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import json
22

33
import httpx
4+
import pytest
45
import respx
56
from pydantic import Field
67

78
from bubble_data_api_client.client.orm import BubbleBaseModel
9+
from bubble_data_api_client.exceptions import UnknownFieldError
810

911

1012
def test_model_instantiation():
@@ -36,3 +38,47 @@ class Order(BubbleBaseModel, typename="order"):
3638
assert route.call_count == 1
3739
request_body = json.loads(route.calls[0].request.content)
3840
assert request_body == {"Buying company": "Acme Corp"}
41+
42+
43+
@respx.mock
44+
async def test_update_single_field(configured_client: None) -> None:
45+
"""Verify update() sends only the specified field."""
46+
47+
class User(BubbleBaseModel, typename="user"):
48+
name: str
49+
email: str
50+
51+
route = respx.patch("https://example.com/user/abc123").mock(return_value=httpx.Response(204))
52+
53+
await User.update(uid="abc123", name="New Name")
54+
55+
assert route.call_count == 1
56+
request_body = json.loads(route.calls[0].request.content)
57+
assert request_body == {"name": "New Name"}
58+
59+
60+
@respx.mock
61+
async def test_update_translates_field_aliases(configured_client: None) -> None:
62+
"""Verify update() translates Python field names to Bubble aliases."""
63+
64+
class Order(BubbleBaseModel, typename="order"):
65+
company: str = Field(alias="Buying company")
66+
status: str
67+
68+
route = respx.patch("https://example.com/order/xyz789").mock(return_value=httpx.Response(204))
69+
70+
await Order.update(uid="xyz789", company="Acme Corp", status="active")
71+
72+
assert route.call_count == 1
73+
request_body = json.loads(route.calls[0].request.content)
74+
assert request_body == {"Buying company": "Acme Corp", "status": "active"}
75+
76+
77+
async def test_update_raises_for_unknown_field() -> None:
78+
"""Verify update() raises UnknownFieldError for fields not in the model."""
79+
80+
class User(BubbleBaseModel, typename="user"):
81+
name: str
82+
83+
with pytest.raises(UnknownFieldError, match="unknown field: nonexistent"):
84+
await User.update(uid="abc123", nonexistent="value")

0 commit comments

Comments
 (0)