Skip to content

Commit c855139

Browse files
committed
Add prefix management
1 parent e23e38b commit c855139

2 files changed

Lines changed: 315 additions & 1 deletion

File tree

terminusdb_client/client/Client.py

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,177 @@ def _get_prefixes(self):
670670
headers=self._default_headers,
671671
auth=self._auth(),
672672
)
673-
return json.loads(_finish_response(result))
673+
result.raise_for_status()
674+
return result.json()
675+
676+
def get_prefix(self, prefix_name: str) -> str:
677+
"""Get a single prefix IRI by name.
678+
679+
Parameters
680+
----------
681+
prefix_name : str
682+
The prefix name to retrieve.
683+
684+
Returns
685+
-------
686+
str
687+
The IRI (namespace URL) this prefix expands to.
688+
689+
Raises
690+
------
691+
DatabaseError
692+
If the prefix does not exist (404) or other API error.
693+
694+
Examples
695+
--------
696+
>>> client.get_prefix("schema")
697+
'http://schema.org/'
698+
"""
699+
self._check_connection()
700+
result = self._session.get(
701+
self._prefix_url(prefix_name),
702+
headers=self._default_headers,
703+
auth=self._auth(),
704+
)
705+
result.raise_for_status()
706+
return result.json()["api:prefix_uri"]
707+
708+
def add_prefix(self, prefix_name: str, uri: str) -> dict:
709+
"""Add a new prefix mapping.
710+
711+
Parameters
712+
----------
713+
prefix_name : str
714+
The prefix name to create (must follow NCName rules).
715+
uri : str
716+
The IRI (namespace URL) this prefix expands to.
717+
718+
Returns
719+
-------
720+
dict
721+
API response with status and details.
722+
723+
Raises
724+
------
725+
DatabaseError
726+
If prefix already exists or validation fails.
727+
728+
Examples
729+
--------
730+
>>> client.add_prefix("ex", "http://example.org/")
731+
{'@type': 'api:PrefixAddResponse', 'api:status': 'api:success', ...}
732+
"""
733+
self._check_connection()
734+
result = self._session.post(
735+
self._prefix_url(prefix_name),
736+
json={"uri": uri},
737+
headers=self._default_headers,
738+
auth=self._auth(),
739+
)
740+
result.raise_for_status()
741+
return result.json()
742+
743+
def update_prefix(self, prefix_name: str, uri: str) -> dict:
744+
"""Update an existing prefix mapping.
745+
746+
Parameters
747+
----------
748+
prefix_name : str
749+
The prefix name to update.
750+
uri : str
751+
The new IRI for this prefix.
752+
753+
Returns
754+
-------
755+
dict
756+
API response with status and details.
757+
758+
Raises
759+
------
760+
DatabaseError
761+
If prefix does not exist (404) or validation fails.
762+
763+
Examples
764+
--------
765+
>>> client.update_prefix("ex", "http://example.com/")
766+
{'@type': 'api:PrefixUpdateResponse', 'api:status': 'api:success', ...}
767+
"""
768+
self._check_connection()
769+
result = self._session.put(
770+
self._prefix_url(prefix_name),
771+
json={"uri": uri},
772+
headers=self._default_headers,
773+
auth=self._auth(),
774+
)
775+
result.raise_for_status()
776+
return result.json()
777+
778+
def upsert_prefix(self, prefix_name: str, uri: str) -> dict:
779+
"""Create or update a prefix mapping (upsert).
780+
781+
Parameters
782+
----------
783+
prefix_name : str
784+
The prefix name.
785+
uri : str
786+
The IRI for this prefix.
787+
788+
Returns
789+
-------
790+
dict
791+
API response with status and details.
792+
793+
Raises
794+
------
795+
DatabaseError
796+
If validation fails.
797+
798+
Examples
799+
--------
800+
>>> client.upsert_prefix("ex", "http://example.org/")
801+
{'@type': 'api:PrefixUpdateResponse', 'api:status': 'api:success', ...}
802+
"""
803+
self._check_connection()
804+
result = self._session.put(
805+
self._prefix_url(prefix_name) + "?create=true",
806+
json={"uri": uri},
807+
headers=self._default_headers,
808+
auth=self._auth(),
809+
)
810+
result.raise_for_status()
811+
return result.json()
812+
813+
def delete_prefix(self, prefix_name: str) -> dict:
814+
"""Delete a prefix mapping.
815+
816+
Parameters
817+
----------
818+
prefix_name : str
819+
The prefix name to delete.
820+
821+
Returns
822+
-------
823+
dict
824+
API response with status.
825+
826+
Raises
827+
------
828+
DatabaseError
829+
If prefix does not exist (404) or is reserved.
830+
831+
Examples
832+
--------
833+
>>> client.delete_prefix("ex")
834+
{'@type': 'api:PrefixDeleteResponse', 'api:status': 'api:success', ...}
835+
"""
836+
self._check_connection()
837+
result = self._session.delete(
838+
self._prefix_url(prefix_name),
839+
headers=self._default_headers,
840+
auth=self._auth(),
841+
)
842+
result.raise_for_status()
843+
return result.json()
674844

675845
def create_database(
676846
self,
@@ -3086,3 +3256,12 @@ def _push_url(self):
30863256

30873257
def _db_url(self):
30883258
return self._db_base("db")
3259+
3260+
def _prefix_url(self, prefix_name: Optional[str] = None):
3261+
"""Get URL for prefix operations"""
3262+
base = self._db_base("prefix")
3263+
if self._db == "_system":
3264+
return base if prefix_name is None else f"{base}/{urlparse.quote(prefix_name)}"
3265+
# For regular databases, include repo and branch
3266+
base = self._branch_base("prefix")
3267+
return base if prefix_name is None else f"{base}/{urlparse.quote(prefix_name)}"
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Integration tests for prefix management operations."""
2+
import pytest
3+
from terminusdb_client.client import Client
4+
5+
6+
@pytest.fixture
7+
def client():
8+
"""Create a test client connected to TerminusDB."""
9+
client = Client("http://127.0.0.1:6363")
10+
client.connect(user="admin", key="root", team="admin")
11+
return client
12+
13+
14+
@pytest.fixture
15+
def test_db(client):
16+
"""Create and cleanup a test database."""
17+
import time
18+
db_name = f"test_prefix_{int(time.time() * 1000)}"
19+
20+
client.create_database(db_name, label="Test Prefix DB", description="Database for testing prefix operations")
21+
client.connect(db=db_name)
22+
23+
yield db_name
24+
25+
# Cleanup
26+
try:
27+
client.delete_database(db_name)
28+
except Exception:
29+
pass
30+
31+
32+
class TestPrefixManagement:
33+
"""Test suite for prefix management operations."""
34+
35+
def test_add_prefix_success(self, client, test_db):
36+
"""Test adding a new prefix successfully."""
37+
result = client.add_prefix("ex", "http://example.org/")
38+
assert result["api:status"] == "api:success"
39+
assert result["api:prefix_name"] == "ex"
40+
assert result["api:prefix_uri"] == "http://example.org/"
41+
42+
def test_add_prefix_duplicate(self, client, test_db):
43+
"""Test that adding duplicate prefix fails."""
44+
client.add_prefix("ex", "http://example.org/")
45+
46+
with pytest.raises(Exception) as exc_info:
47+
client.add_prefix("ex", "http://example.com/")
48+
49+
# Should get 400 error with PrefixAlreadyExists
50+
assert exc_info.value.response.status_code == 400
51+
52+
def test_add_prefix_invalid_iri(self, client, test_db):
53+
"""Test that invalid IRI is rejected."""
54+
with pytest.raises(Exception) as exc_info:
55+
client.add_prefix("ex", "not-a-valid-uri")
56+
57+
assert exc_info.value.response.status_code == 400
58+
59+
def test_add_prefix_reserved(self, client, test_db):
60+
"""Test that reserved prefix names are rejected."""
61+
with pytest.raises(Exception) as exc_info:
62+
client.add_prefix("@custom", "http://example.org/")
63+
64+
assert exc_info.value.response.status_code == 400
65+
66+
def test_get_prefix_success(self, client, test_db):
67+
"""Test retrieving an existing prefix."""
68+
client.add_prefix("ex", "http://example.org/")
69+
uri = client.get_prefix("ex")
70+
assert uri == "http://example.org/"
71+
72+
def test_get_prefix_not_found(self, client, test_db):
73+
"""Test that getting non-existent prefix fails."""
74+
with pytest.raises(Exception) as exc_info:
75+
client.get_prefix("nonexistent")
76+
77+
assert exc_info.value.response.status_code == 404
78+
79+
def test_update_prefix_success(self, client, test_db):
80+
"""Test updating an existing prefix."""
81+
client.add_prefix("ex", "http://example.org/")
82+
result = client.update_prefix("ex", "http://example.com/")
83+
84+
assert result["api:status"] == "api:success"
85+
assert result["api:prefix_uri"] == "http://example.com/"
86+
87+
# Verify the update
88+
uri = client.get_prefix("ex")
89+
assert uri == "http://example.com/"
90+
91+
def test_update_prefix_not_found(self, client, test_db):
92+
"""Test that updating non-existent prefix fails."""
93+
with pytest.raises(Exception) as exc_info:
94+
client.update_prefix("nonexistent", "http://example.org/")
95+
96+
assert exc_info.value.response.status_code == 404
97+
98+
def test_upsert_prefix_create(self, client, test_db):
99+
"""Test upsert creates prefix if it doesn't exist."""
100+
result = client.upsert_prefix("ex", "http://example.org/")
101+
assert result["api:status"] == "api:success"
102+
assert result["api:prefix_uri"] == "http://example.org/"
103+
104+
def test_upsert_prefix_update(self, client, test_db):
105+
"""Test upsert updates prefix if it already exists."""
106+
client.add_prefix("ex", "http://example.org/")
107+
result = client.upsert_prefix("ex", "http://example.com/")
108+
109+
assert result["api:status"] == "api:success"
110+
assert result["api:prefix_uri"] == "http://example.com/"
111+
112+
def test_delete_prefix_success(self, client, test_db):
113+
"""Test deleting an existing prefix."""
114+
client.add_prefix("ex", "http://example.org/")
115+
result = client.delete_prefix("ex")
116+
assert result["api:status"] == "api:success"
117+
118+
# Verify deletion
119+
with pytest.raises(Exception) as exc_info:
120+
client.get_prefix("ex")
121+
assert exc_info.value.response.status_code == 404
122+
123+
def test_delete_prefix_not_found(self, client, test_db):
124+
"""Test that deleting non-existent prefix fails."""
125+
with pytest.raises(Exception) as exc_info:
126+
client.delete_prefix("nonexistent")
127+
128+
assert exc_info.value.response.status_code == 404
129+
130+
def test_delete_prefix_reserved(self, client, test_db):
131+
"""Test that deleting reserved prefix fails."""
132+
with pytest.raises(Exception) as exc_info:
133+
client.delete_prefix("@base")
134+
135+
assert exc_info.value.response.status_code == 400

0 commit comments

Comments
 (0)