Skip to content

Commit 228b36a

Browse files
Merge pull request #339 from terminusdb/add_log_endpoint
Add log, apply and diff_version endpoint and refactor old functions
2 parents b958466 + d28e9c7 commit 228b36a

File tree

2 files changed

+204
-63
lines changed

2 files changed

+204
-63
lines changed

terminusdb_client/client/Client.py

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,53 @@ def _check_connection(self, check_db=True) -> None:
391391
"No database is connected. Please either connect to a database or create a new database."
392392
)
393393

394+
def log(self,
395+
team: Optional[str] = None,
396+
db: Optional[str] = None,
397+
start: int = 0,
398+
count: int = -1):
399+
"""Get commit history of a database
400+
Parameters
401+
----------
402+
team: str, optional
403+
The team from which the database is. Defaults to the class property.
404+
db: str, optional
405+
The database. Defaults to the class property.
406+
start: int, optional
407+
Commit index to start from. Defaults to 0.
408+
count: int, optional
409+
Amount of commits to get. Defaults to -1 which gets all.
410+
411+
Returns
412+
-------
413+
list
414+
415+
List of the following commit objects:
416+
{
417+
"@id":"InitialCommit/hpl18q42dbnab4vzq8me4bg1xn8p2a0",
418+
"@type":"InitialCommit",
419+
"author":"system",
420+
"identifier":"hpl18q42dbnab4vzq8me4bg1xn8p2a0",
421+
"message":"create initial schema",
422+
"schema":"layer_data:Layer_4234adfe377fa9563a17ad764ac37f5dcb14de13668ea725ef0748248229a91b",
423+
"timestamp":1660919664.9129035
424+
}
425+
"""
426+
self._check_connection(check_db=(not team or not db))
427+
team = team if team else self.team
428+
db = db if db else self.db
429+
result = requests.get(
430+
f"{self.api}/log/{team}/{db}",
431+
params={'start': start, 'count': count},
432+
headers=self._default_headers,
433+
auth=self._auth(),
434+
)
435+
commits = json.loads(_finish_response(result))
436+
for commit in commits:
437+
commit['timestamp'] = datetime.fromtimestamp(commit['timestamp'])
438+
commit['commit'] = commit['identifier'] # For backwards compat.
439+
return commits
440+
394441
def get_commit_history(self, max_history: int = 500) -> list:
395442
"""Get the whole commit history.
396443
Commit history - Commit id, author of the commit, commit message and the commit time, in the current branch from the current commit, ordered backwards in time, will be returned in a dictionary in the follow format:
@@ -403,7 +450,7 @@ def get_commit_history(self, max_history: int = 500) -> list:
403450
Parameters
404451
----------
405452
max_history: int, optional
406-
maximum number of commit that would return, counting backwards from your current commit. Default is set to 500. It need to be nop-negitive, if input is 0 it will still give the last commit.
453+
maximum number of commit that would return, counting backwards from your current commit. Default is set to 500. It needs to be nop-negative, if input is 0 it will still give the last commit.
407454
408455
Example
409456
-------
@@ -429,70 +476,21 @@ def get_commit_history(self, max_history: int = 500) -> list:
429476
"""
430477
if max_history < 0:
431478
raise ValueError("max_history needs to be non-negative.")
432-
if max_history > 1:
433-
limit_history = max_history - 1
434-
else:
435-
limit_history = 1
436-
woql_query = (
437-
WOQLQuery()
438-
.using("_commits")
439-
.limit(limit_history)
440-
.triple("v:branch", "name", WOQLQuery().string(self.branch))
441-
.triple("v:branch", "head", "v:commit")
442-
.path("v:commit", "parent*", "v:target_commit")
443-
.triple("v:target_commit", "identifier", "v:cid")
444-
.triple("v:target_commit", "author", "v:author")
445-
.triple("v:target_commit", "message", "v:message")
446-
.triple("v:target_commit", "timestamp", "v:timestamp")
447-
)
448-
result = self.query(woql_query).get("bindings")
449-
if not result:
450-
return result
451-
else:
452-
result_list = []
453-
for result_item in result:
454-
result_list.append(
455-
{
456-
"commit": result_item["cid"]["@value"],
457-
"author": result_item["author"]["@value"],
458-
"message": result_item["message"]["@value"],
459-
"timestamp": datetime.fromtimestamp(
460-
int(result_item["timestamp"]["@value"])
461-
),
462-
}
463-
)
464-
return result_list
479+
return self.log(count=max_history)
465480

466481
def _get_current_commit(self):
467-
woql_query = (
468-
WOQLQuery()
469-
.using("_commits")
470-
.triple("v:branch", "name", WOQLQuery().string(self.branch))
471-
.triple("v:branch", "head", "v:commit")
472-
.triple("v:commit", "identifier", "v:cid")
473-
)
474-
result = self.query(woql_query)
475-
if not result:
476-
return None
477-
current_commit = result.get("bindings")[0].get("cid").get("@value")
478-
return current_commit
482+
descriptor = self.db
483+
if self.branch:
484+
descriptor = f'{descriptor}/local/branch/{self.branch}'
485+
commit = self.log(team=self.team, db=descriptor, count=1)[0]
486+
return commit['identifier']
479487

480488
def _get_target_commit(self, step):
481-
woql_query = (
482-
WOQLQuery()
483-
.using("_commits")
484-
.path(
485-
"v:commit",
486-
f"parent{{{step},{step}}}",
487-
"v:target_commit",
488-
)
489-
.triple("v:branch", "name", WOQLQuery().string(self.branch))
490-
.triple("v:branch", "head", "v:commit")
491-
.triple("v:target_commit", "identifier", "v:cid")
492-
)
493-
result = self.query(woql_query)
494-
target_commit = result.get("bindings")[0].get("cid").get("@value")
495-
return target_commit
489+
descriptor = self.db
490+
if self.branch:
491+
descriptor = f'{descriptor}/local/branch/{self.branch}'
492+
commit = self.log(team=self.team, db=descriptor, count=1, start=step)[0]
493+
return commit['identifier']
496494

497495
def get_all_branches(self, get_data_version=False):
498496
"""Get all the branches available in the database."""
@@ -2014,6 +2012,86 @@ def _convert_diff_document(self, document):
20142012
new_doc = self._conv_to_dict(document)
20152013
return new_doc
20162014

2015+
def apply(self,
2016+
before_version,
2017+
after_version,
2018+
branch=None,
2019+
message=None,
2020+
author=None):
2021+
"""Diff two different commits and apply changes on branch
2022+
2023+
Parameters
2024+
----------
2025+
before_version : string
2026+
Before branch/commit to compare
2027+
after_object : string
2028+
After branch/commit to compare
2029+
branch : string
2030+
Branch to apply to. Optional.
2031+
"""
2032+
self._check_connection()
2033+
branch = branch if branch else self.branch
2034+
return json.loads(
2035+
_finish_response(
2036+
requests.post(
2037+
self._apply_url(branch=branch),
2038+
headers=self._default_headers,
2039+
json={
2040+
"commit_info": self._generate_commit(message, author),
2041+
"before_commit": before_version,
2042+
"after_commit": after_version,
2043+
},
2044+
auth=self._auth(),
2045+
)
2046+
)
2047+
)
2048+
2049+
def diff_object(self, before_object, after_object):
2050+
"""Diff two different objects.
2051+
2052+
Parameters
2053+
----------
2054+
before_object : string
2055+
Before object to compare
2056+
after_object : string
2057+
After object to compare
2058+
"""
2059+
self._check_connection(check_db=False)
2060+
return json.loads(
2061+
_finish_response(
2062+
requests.post(
2063+
self._diff_url(),
2064+
headers=self._default_headers,
2065+
json={'before': before_object,
2066+
'after': after_object},
2067+
auth=self._auth(),
2068+
)
2069+
)
2070+
)
2071+
2072+
def diff_version(self, before_version, after_version):
2073+
"""Diff two different versions. Can either be a branch or a commit
2074+
2075+
Parameters
2076+
----------
2077+
before_version : string
2078+
Commit or branch of the before version to compare
2079+
after_version : string
2080+
Commit or branch of the after version to compare
2081+
"""
2082+
self._check_connection(check_db=False)
2083+
return json.loads(
2084+
_finish_response(
2085+
requests.post(
2086+
self._diff_url(),
2087+
headers=self._default_headers,
2088+
json={'before_data_version': before_version,
2089+
'after_data_version': after_version},
2090+
auth=self._auth(),
2091+
)
2092+
)
2093+
)
2094+
20172095
def diff(
20182096
self,
20192097
before: Union[
@@ -2034,7 +2112,9 @@ def diff(
20342112
],
20352113
document_id: Union[str, None] = None,
20362114
):
2037-
"""Perform diff on 2 set of document(s), result in a Patch object.
2115+
"""DEPRECATED
2116+
2117+
Perform diff on 2 set of document(s), result in a Patch object.
20382118
20392119
Do not connect when using public API.
20402120
@@ -2790,14 +2870,16 @@ def _branch_url(self, branch_id: str):
27902870
def _repo_base(self, action: str):
27912871
return self._db_base(action) + f"/{self._repo}"
27922872

2793-
def _branch_base(self, action: str):
2873+
def _branch_base(self, action: str, branch: Optional[str] = None):
27942874
base = self._repo_base(action)
27952875
if self._repo == "_meta":
27962876
return base
27972877
if self._branch == "_commits":
27982878
return base + f"/{self._branch}"
27992879
elif self.ref:
28002880
return base + f"/commit/{self._ref}"
2881+
elif branch:
2882+
return base + f"/branch/{branch}"
28012883
else:
28022884
return base + f"/branch/{self._branch}"
28032885
return base
@@ -2870,6 +2952,9 @@ def _squash_url(self):
28702952
def _diff_url(self):
28712953
return self._branch_base("diff")
28722954

2955+
def _apply_url(self, branch: Optional[str] = None):
2956+
return self._branch_base("apply", branch)
2957+
28732958
def _patch_url(self):
28742959
return self._branch_base("patch")
28752960

terminusdb_client/tests/integration_tests/test_client.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,62 @@ def test_add_get_remove_org(docker_url):
110110
client.get_organization("testOrg")
111111

112112

113+
def test_diff_object(docker_url):
114+
# create client
115+
client = Client(docker_url, user_agent=test_user_agent)
116+
# test create db
117+
client.connect()
118+
diff = client.diff_object({'test': 'wew'}, {'test': 'wow'})
119+
assert diff == {'test': {'@before': 'wew', '@after': 'wow', '@op': 'SwapValue'}}
120+
121+
122+
def test_diff_apply_version(docker_url):
123+
client = Client(docker_url, user_agent=test_user_agent)
124+
client.connect()
125+
db_name = "philosophers" + str(random())
126+
client.create_database(db_name)
127+
client.connect(db=db_name)
128+
# Add a philosopher schema
129+
schema = {"@type": "Class",
130+
"@id": "Philosopher",
131+
"name": "xsd:string"
132+
}
133+
# Add schema and Socrates
134+
client.insert_document(schema, graph_type="schema")
135+
client.insert_document({"name": "Socrates"})
136+
137+
# Create new branch and switch to it
138+
client.create_branch("changes")
139+
client.branch = "changes"
140+
141+
# Add more philosophers
142+
client.insert_document({"name": "Plato"})
143+
client.insert_document({"name": "Aristotle"})
144+
145+
diff = client.diff_version("main", "changes")
146+
assert len(diff) == 2
147+
assert diff[0]['@insert']['name'] == 'Plato'
148+
assert diff[1]['@insert']['name'] == 'Aristotle'
149+
150+
# Apply the differences to main with apply
151+
client.apply("main", "changes", branch='main')
152+
153+
# Diff again
154+
diff_again = client.diff_version("main", "changes")
155+
assert len(diff_again) == 0
156+
157+
158+
def test_log(docker_url):
159+
# create client
160+
client = Client(docker_url, user_agent=test_user_agent)
161+
# test create db
162+
client.connect()
163+
db_name = "testDB" + str(random())
164+
client.create_database(db_name, team="admin")
165+
log = client.log(team="admin", db=db_name)
166+
assert log[0]['@type'] == 'InitialCommit'
167+
168+
113169
def test_get_database(docker_url):
114170
client = Client(docker_url, user_agent=test_user_agent, team="admin")
115171
client.connect()

0 commit comments

Comments
 (0)