Skip to content

Commit

Permalink
Implement commit
Browse files Browse the repository at this point in the history
  • Loading branch information
disrupted committed Dec 14, 2024
1 parent 04e0c79 commit af195b1
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 35 deletions.
66 changes: 60 additions & 6 deletions tests/test_todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,63 @@ def test_todo_from_api_object():
assert todo._api_object == api_object


# def test_update():
# todo = TodoItem(title="original")
# update = TodoItem(title="updated")
# keys = {"tt"}
# todo.update(update, keys)
# assert todo.title == "updated"
def test_to_new(task: TodoItem):
assert task._api_object is None
new = task.to_new()
# we simulate what happens when we commit the changes
task._commit(new)
assert task._api_object
assert task._api_object.title == "test task"


def test_to_new_exists(task: TodoItem):
assert task._api_object is None
new = task.to_new()
# we simulate what happens when we commit the changes
task._commit(new)

with pytest.raises(
ValueError, match="^current version exists for todo, use to_edit instead$"
):
task.to_new()


def test_to_edit_does_not_exist(task: TodoItem):
with pytest.raises(
ValueError, match="^no current version exists for todo, use to_new instead$"
):
task.to_edit()


def test_to_edit_unchanged(task: TodoItem):
assert task._api_object is None
new = task.to_new()
# we simulate what happens when we commit the changes
task._commit(new)

with pytest.raises(ValueError, match="^no changes found$"):
task.to_edit()


def test_to_edit(task: TodoItem):
assert task._api_object is None
new = task.to_new()
# we simulate what happens when we commit the changes
task._commit(new)

task.title = "updated task"
delta = task.to_edit()
assert delta
assert delta.model_dump(exclude_none=True) == {"title": "updated task"}


def test_to_edit_detect_reverts(task: TodoItem):
assert task._api_object is None
new = task.to_new()
# we simulate what happens when we commit the changes
task._commit(new)

task.title = "updated task" # first we change something
task.title = "test task" # but then we revert to the old value
with pytest.raises(ValueError, match="no changes found"):
task.to_edit()
36 changes: 19 additions & 17 deletions things_cloud/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

from things_cloud.api.const import API_BASE, HEADERS
from things_cloud.api.exceptions import ThingsCloudException
from things_cloud.models.serde import JsonSerde
from things_cloud.models.todo import (
CommitResponse,
EditBody,
HistoryResponse,
NewBody,
TodoItem,
Update,
UpdateType,
)
from things_cloud.utils import Util
Expand Down Expand Up @@ -121,41 +123,41 @@ def today(self) -> list[TodoItem]:
def __commit(
self,
index: int,
data: dict | None = None,
) -> int:
update: Update,
) -> CommitResponse:
response = self.__request(
method="POST",
endpoint="/commit",
params={
"ancestor-index": str(index),
"_cnt": "1",
},
content=JsonSerde.dumps(data),
content=update.to_api_payload(),
)
return response.json()["server-head-index"]
return CommitResponse.model_validate_json(response.read())

def __create_todo(self, index: int, item: TodoItem) -> None:
data = {item.uuid: {"t": 0, "e": "Task6", "p": serialize_dict(item)}}
log.debug("", data=data)
complete = item.to_new()
body = NewBody(payload=complete)
update = Update(id=item.uuid, body=body)

try:
self._offset = self.__commit(index, data)
item.reset_changes()
commit = self.__commit(index, update)
self._offset = commit.server_head_index
item._commit(complete)
except ThingsCloudException as e:
log.error("Error creating todo")
raise e

def __modify_todo(self, index: int, item: TodoItem) -> None:
changes = item.changes
if not changes:
log.warning("there are no changes to be sent")
return
data = {item.uuid: {"t": 1, "e": "Task6", "p": serialize_dict(item, changes)}}
log.debug("", data=data)
delta = item.to_edit()
body = EditBody(payload=delta)
update = Update(id=item.uuid, body=body)

try:
self._offset = self.__commit(index, data)
item.reset_changes()
commit = self.__commit(index, update)
self._offset = commit.server_head_index
item._commit(delta)
except ThingsCloudException as e:
log.error("Error modifying todo")
raise e
61 changes: 49 additions & 12 deletions things_cloud/models/todo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
ShortUUID = Annotated[str, pydantic.StringConstraints(min_length=22, max_length=22)]


class CommitResponse(pydantic.BaseModel):
server_head_index: Annotated[
pydantic.PositiveInt, pydantic.Field(alias="server-head-index")
]


class HistoryResponse(pydantic.BaseModel):
current_item_index: Annotated[
pydantic.PositiveInt, pydantic.Field(alias="current-item-index")
Expand All @@ -38,17 +44,6 @@ def updates(self) -> Iterator[Update]:
key, value = next(iter(item.items()))
yield Update(id=key, body=value)

# @pydantic.model_validator(mode="before")
# def flatten_items(cls, values: dict[str, Any]) -> dict[str, Any]:
# items = values.get("items")
# assert (
# isinstance(items, dict) and len(items) == 1
# ), "malformed API response, expected items dict with one key-value pair"

# key, value = next(iter(items.items()))
# values["items"] = Update(id=key, payload=value)
# return values


class Update(pydantic.BaseModel):
id: ShortUUID
Expand All @@ -59,6 +54,9 @@ class Update(pydantic.BaseModel):
# self.body.payload._uuid = self.id
# return self

def to_api_payload(self) -> dict[ShortUUID, dict[str, Any]]:
return {self.id: self.body.to_api_payload()}


class UpdateType(IntEnum):
NEW = 0
Expand All @@ -74,6 +72,9 @@ class NewBody(pydantic.BaseModel):
payload: Annotated[TodoApiObject, pydantic.Field(alias="p")]
entity: Annotated[EntityType, pydantic.Field(alias="e")] = EntityType.TASK_6

def to_api_payload(self) -> dict[str, Any]:
return self.model_dump(mode="json", by_alias=True)


class EditBody(pydantic.BaseModel):
type: Annotated[Literal[UpdateType.EDIT], pydantic.Field(alias="t")] = (
Expand All @@ -82,6 +83,9 @@ class EditBody(pydantic.BaseModel):
payload: Annotated[TodoDeltaApiObject, pydantic.Field(alias="p")]
entity: Annotated[EntityType, pydantic.Field(alias="e")] = EntityType.TASK_6

def to_api_payload(self) -> dict[str, Any]:
return self.model_dump(mode="json", by_alias=True, exclude_none=True)


Body = Annotated[NewBody | EditBody, pydantic.Field(discriminator="type")]

Expand Down Expand Up @@ -310,7 +314,13 @@ class TodoItem(pydantic.BaseModel):
note: Note = pydantic.Field(default_factory=Note)
_api_object: TodoApiObject | None = pydantic.PrivateAttr(default=None)

def to_api_object(self) -> TodoApiObject:
def to_new(self) -> TodoApiObject:
if self._api_object:
msg = (
f"current version exists for todo, use {self.to_edit.__name__} instead"
)
raise ValueError(msg)

return TodoApiObject(
index=self.index,
title=self.title,
Expand Down Expand Up @@ -347,6 +357,33 @@ def to_api_object(self) -> TodoApiObject:
note=self.note,
)

def to_edit(self) -> TodoDeltaApiObject:
if not self._api_object:
msg = f"no current version exists for todo, use {self.to_new.__name__} instead"
raise ValueError(msg)

keys = self.model_dump(by_alias=False).keys()
edits = {}
for key in keys:
current_value = getattr(self._api_object, key)
new_value = getattr(self, key)
if current_value == new_value:
continue
edits[key] = new_value
if not edits:
raise ValueError("no changes found")
return TodoDeltaApiObject.model_validate(edits)

def _commit(self, complete_or_delta: TodoApiObject | TodoDeltaApiObject) -> None:
if isinstance(complete_or_delta, TodoApiObject):
self._api_object = complete_or_delta

else:
delta = complete_or_delta.model_dump(by_alias=False, exclude_none=True)
for key in delta.keys():
new_value = getattr(delta, key)
setattr(self._api_object, key, new_value)

@property
def uuid(self) -> ShortUUID:
return self._uuid
Expand Down

0 comments on commit af195b1

Please sign in to comment.