Skip to content

Commit b8dee71

Browse files
committed
feat: Add document history command to python client
1 parent e23e38b commit b8dee71

File tree

3 files changed

+203
-0
lines changed

3 files changed

+203
-0
lines changed

terminusdb_client/client/Client.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,109 @@ def get_commit_history(self, max_history: int = 500) -> list:
563563
raise ValueError("max_history needs to be non-negative.")
564564
return self.log(count=max_history)
565565

566+
def get_document_history(
567+
self,
568+
doc_id: str,
569+
team: Optional[str] = None,
570+
db: Optional[str] = None,
571+
start: int = 0,
572+
count: int = -1,
573+
created: bool = False,
574+
updated: bool = False,
575+
) -> list:
576+
"""Get the commit history for a specific document
577+
578+
Returns the history of changes made to a document, ordered backwards
579+
in time from the most recent change. Only commits where the specified
580+
document was created, modified, or deleted are included.
581+
582+
Parameters
583+
----------
584+
doc_id : str
585+
The document ID (IRI) to retrieve history for (e.g., "Person/alice")
586+
team : str, optional
587+
The team from which the database is. Defaults to the class property.
588+
db : str, optional
589+
The database. Defaults to the class property.
590+
start : int, optional
591+
Starting index for pagination. Defaults to 0.
592+
count : int, optional
593+
Maximum number of history entries to return. Defaults to -1 (all).
594+
created : bool, optional
595+
If True, return only the creation time. Defaults to False.
596+
updated : bool, optional
597+
If True, return only the last update time. Defaults to False.
598+
599+
Raises
600+
------
601+
InterfaceError
602+
If the client is not connected to a database
603+
DatabaseError
604+
If the API request fails or document is not found
605+
606+
Returns
607+
-------
608+
list
609+
List of history entry dictionaries containing commit information
610+
for the specified document:
611+
```
612+
[
613+
{
614+
"author": "admin",
615+
"identifier": "tbn15yq6rw1l4e9bgboyu3vwcoxgri5",
616+
"message": "Updated document",
617+
"timestamp": datetime.datetime(2023, 4, 6, 19, 1, 14, 324928)
618+
},
619+
{
620+
"author": "admin",
621+
"identifier": "3v3naa8jrt8612dg5zryu4vjqwa2w9s",
622+
"message": "Created document",
623+
"timestamp": datetime.datetime(2023, 4, 6, 19, 0, 47, 406387)
624+
}
625+
]
626+
```
627+
628+
Example
629+
-------
630+
>>> from terminusdb_client import Client
631+
>>> client = Client("http://127.0.0.1:6363")
632+
>>> client.connect(db="example_db")
633+
>>> history = client.get_document_history("Person/Jane")
634+
>>> print(f"Document modified {len(history)} times")
635+
>>> print(f"Last change by: {history[0]['author']}")
636+
"""
637+
self._check_connection(check_db=(not team or not db))
638+
team = team if team else self.team
639+
db = db if db else self.db
640+
641+
params = {
642+
'id': doc_id,
643+
'start': start,
644+
'count': count,
645+
}
646+
647+
if created:
648+
params['created'] = created
649+
if updated:
650+
params['updated'] = updated
651+
652+
result = self._session.get(
653+
f"{self.api}/history/{team}/{db}",
654+
params=params,
655+
headers=self._default_headers,
656+
auth=self._auth(),
657+
)
658+
659+
history = json.loads(_finish_response(result))
660+
661+
# Post-process timestamps from Unix timestamp to datetime objects
662+
if isinstance(history, list):
663+
for entry in history:
664+
if 'timestamp' in entry and isinstance(entry['timestamp'], (int, float)):
665+
entry['timestamp'] = datetime.fromtimestamp(entry['timestamp'])
666+
667+
return history
668+
566669
def _get_current_commit(self):
567670
descriptor = self.db
568671
if self.branch:

terminusdb_client/tests/integration_tests/test_client.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,70 @@ def test_log(docker_url):
211211
assert log[0]['@type'] == 'InitialCommit'
212212

213213

214+
def test_get_document_history(docker_url):
215+
# Create client
216+
client = Client(docker_url, user_agent=test_user_agent)
217+
client.connect()
218+
219+
# Create test database
220+
db_name = "testDB" + str(random())
221+
client.create_database(db_name, team="admin")
222+
client.connect(db=db_name)
223+
224+
# Add a schema
225+
schema = {
226+
"@type": "Class",
227+
"@id": "Person",
228+
"name": "xsd:string",
229+
"age": "xsd:integer"
230+
}
231+
client.insert_document(schema, graph_type=GraphType.SCHEMA)
232+
233+
# Insert a document
234+
person = {"@type": "Person", "@id": "Person/Jane", "name": "Jane", "age": 30}
235+
client.insert_document(person, commit_msg="Created Person/Jane")
236+
237+
# Update the document to create history
238+
person["name"] = "Jane Doe"
239+
person["age"] = 31
240+
client.update_document(person, commit_msg="Updated Person/Jane name and age")
241+
242+
# Update again
243+
person["age"] = 32
244+
client.update_document(person, commit_msg="Updated Person/Jane age")
245+
246+
# Get document history
247+
history = client.get_document_history("Person/Jane")
248+
249+
# Assertions
250+
assert isinstance(history, list)
251+
assert len(history) >= 3 # At least insert and two updates
252+
assert all('timestamp' in entry for entry in history)
253+
assert all(isinstance(entry['timestamp'], dt.datetime) for entry in history)
254+
assert all('author' in entry for entry in history)
255+
assert all('message' in entry for entry in history)
256+
assert all('identifier' in entry for entry in history)
257+
258+
# Verify messages are in the history (order may vary)
259+
messages = [entry['message'] for entry in history]
260+
assert "Created Person/Jane" in messages
261+
assert "Updated Person/Jane name and age" in messages
262+
assert "Updated Person/Jane age" in messages
263+
264+
# Test with pagination
265+
paginated_history = client.get_document_history("Person/Jane", start=0, count=2)
266+
assert len(paginated_history) == 2
267+
268+
# Test with team/db override
269+
history_override = client.get_document_history(
270+
"Person/Jane", team="admin", db=db_name
271+
)
272+
assert len(history_override) == len(history)
273+
274+
# Cleanup
275+
client.delete_database(db_name, "admin")
276+
277+
214278
def test_get_triples(docker_url):
215279
client = Client(docker_url, user_agent=test_user_agent, team="admin")
216280
client.connect()

terminusdb_client/tests/test_Client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,39 @@ def test_get_user(mocked_requests):
712712
auth=("admin", "root"),
713713
headers={"user-agent": f"terminusdb-client-python/{__version__}"},
714714
)
715+
716+
717+
@mock.patch.object(requests.Session, 'head', side_effect=mocked_request_success)
718+
@mock.patch.object(requests.Session, 'get', side_effect=mocked_request_success)
719+
def test_get_document_history(mocked_get, mocked_head):
720+
client = Client(
721+
"http://localhost:6363", user="admin", key="root", team="admin"
722+
)
723+
client.connect(db="myDBName")
724+
725+
client.get_document_history("Person/Jane", start=0, count=10)
726+
727+
# Get the last call to get (should be our get_document_history call)
728+
last_call = client._session.get.call_args_list[-1]
729+
assert last_call[0][0] == "http://localhost:6363/api/history/admin/myDBName"
730+
assert last_call[1]["params"] == {"id": "Person/Jane", "start": 0, "count": 10}
731+
assert last_call[1]["headers"] == {"user-agent": f"terminusdb-client-python/{__version__}"}
732+
assert last_call[1]["auth"] == ("admin", "root")
733+
734+
735+
@mock.patch.object(requests.Session, 'head', side_effect=mocked_request_success)
736+
@mock.patch.object(requests.Session, 'get', side_effect=mocked_request_success)
737+
def test_get_document_history_with_created_updated(mocked_get, mocked_head):
738+
client = Client(
739+
"http://localhost:6363", user="admin", key="root", team="admin"
740+
)
741+
client.connect(db="myDBName")
742+
743+
client.get_document_history("Person/Jane", created=True, updated=True)
744+
745+
# Get the last call to get (should be our get_document_history call)
746+
last_call = client._session.get.call_args_list[-1]
747+
assert last_call[0][0] == "http://localhost:6363/api/history/admin/myDBName"
748+
assert last_call[1]["params"] == {"id": "Person/Jane", "start": 0, "count": -1, "created": True, "updated": True}
749+
assert last_call[1]["headers"] == {"user-agent": f"terminusdb-client-python/{__version__}"}
750+
assert last_call[1]["auth"] == ("admin", "root")

0 commit comments

Comments
 (0)