diff --git a/pysirix/__init__.py b/pysirix/__init__.py index 66309fd..9fb34a7 100644 --- a/pysirix/__init__.py +++ b/pysirix/__init__.py @@ -16,6 +16,7 @@ DeleteDiff, Metadata, MetaNode, + PathSummaryNode, ) @@ -66,4 +67,5 @@ async def sirix_async( "Metadata", "MetaNode", "TimeAxisShift", + "PathSummaryNode", ] diff --git a/pysirix/async_client.py b/pysirix/async_client.py index d3ce5df..d647e4d 100644 --- a/pysirix/async_client.py +++ b/pysirix/async_client.py @@ -42,7 +42,7 @@ async def create_database(self, name: str, db_type: DBType) -> None: resp.raise_for_status() async def get_database_info(self, name: str) -> Dict: - resp = await self.client.get(name) + resp = await self.client.get(name, headers={"Accept": "application/json"}) with include_response_text_in_errors(): resp.raise_for_status() return resp.json() @@ -74,12 +74,18 @@ async def create_resource( hash_type: str = "ROLLING", use_dewey_ids: bool = False, hash_kind: str = None, + commit_message: str = None, + commit_timestamp: str = None, ) -> str: params = {"hashType": hash_type} if use_dewey_ids: params["useDeweyIDs"] = "true" if hash_kind is not None: params["hashKind"] = hash_kind + if commit_message is not None: + params["commitMessage"] = commit_message + if commit_timestamp is not None: + params["commitTimestamp"] = commit_timestamp resp = await self.client.put( f"{db_name}/{name}", headers={"Content-Type": db_type.value}, @@ -90,6 +96,31 @@ async def create_resource( resp.raise_for_status() return resp.text + async def create_resources( + self, + db_name: str, + db_type: DBType, + resources: Dict[str, BytesLikeAsync], + hash_type: str = "ROLLING", + use_dewey_ids: bool = False, + ) -> str: + content_type = db_type.value + files = [ + ("resource", (name, data, content_type)) + for name, data in resources.items() + ] + params = {"hashType": hash_type} + if use_dewey_ids: + params["useDeweyIDs"] = "true" + resp = await self.client.post( + db_name, + files=files, + params=params, + ) + with include_response_text_in_errors(): + resp.raise_for_status() + return resp.text + async def read_resource( self, db_name: str, @@ -107,14 +138,48 @@ async def read_resource( else: return ET.fromstring(resp.text) - async def history(self, db_name: str, db_type: DBType, name: str) -> List[Commit]: + async def history( + self, + db_name: str, + db_type: DBType, + name: str, + revisions: int = None, + start_revision: int = None, + end_revision: int = None, + ) -> List[Commit]: + params = {} + if revisions is not None: + params["revisions"] = revisions + if start_revision is not None: + params["startRevision"] = start_revision + if end_revision is not None: + params["endRevision"] = end_revision resp = await self.client.get( - f"{db_name}/{name}/history", headers={"Accept": db_type.value} + f"{db_name}/{name}/history", params=params, headers={"Accept": db_type.value} ) with include_response_text_in_errors(): resp.raise_for_status() return resp.json()["history"] + async def path_summary( + self, + db_name: str, + db_type: DBType, + name: str, + revision: int = None, + ) -> Dict: + params = {} + if revision is not None: + params["revision"] = revision + resp = await self.client.get( + f"{db_name}/{name}/pathSummary", + params=params, + headers={"Accept": db_type.value}, + ) + with include_response_text_in_errors(): + resp.raise_for_status() + return resp.json() + async def diff( self, db_name: str, name: str, params: Dict[str, str] ) -> List[Dict[str, Union[InsertDiff, ReplaceDiff, UpdateDiff, int]]]: @@ -152,12 +217,19 @@ async def update( data: BytesLikeAsync, insert: Insert, etag: Union[str, None], + commit_message: str = None, + commit_timestamp: str = None, ) -> str: if not etag: etag = await self.get_etag(db_name, db_type, name, {"nodeId": node_id}) + params = {"nodeId": node_id, "insert": insert.value} + if commit_message is not None: + params["commitMessage"] = commit_message + if commit_timestamp is not None: + params["commitTimestamp"] = commit_timestamp resp = await self.client.post( f"{db_name}/{name}", - params={"nodeId": node_id, "insert": insert.value}, + params=params, headers={"ETag": etag, "Content-Type": db_type.value}, content=data, ) diff --git a/pysirix/database.py b/pysirix/database.py index fac2172..42bd534 100644 --- a/pysirix/database.py +++ b/pysirix/database.py @@ -6,6 +6,7 @@ from pysirix.sync_client import SyncClient from pysirix.async_client import AsyncClient from pysirix.resource import Resource +from pysirix.types import BytesLike class Database: @@ -82,6 +83,28 @@ def json_store(self, name: str, root: str = ""): else: return JsonStoreSync(self.database_name, name, self._client, self._auth, root) + def create_resources( + self, + resources: Dict[str, Union[str, BytesLike]], + hash_type: str = "ROLLING", + use_dewey_ids: bool = False, + ): + """ + Create multiple resources in this database via multipart upload. + + :param resources: a dict mapping resource names to their data content. + :param hash_type: the hash type to use. + :param use_dewey_ids: whether to use DeweyIDs for node identification. + :return: a ``str`` response. + """ + return self._client.create_resources( + self.database_name, + self.database_type, + resources, + hash_type, + use_dewey_ids, + ) + def delete(self) -> Union[Awaitable[None], None]: """ Delete the database with the name of this :py:class:`Database` instance. diff --git a/pysirix/resource.py b/pysirix/resource.py index e2ef3b0..4a42797 100644 --- a/pysirix/resource.py +++ b/pysirix/resource.py @@ -42,7 +42,15 @@ def __init__( self._client = client self._auth = auth - def create(self, data: Union[str, Dict, ET.Element], hash_type: str = "ROLLING", use_dewey_ids: bool = False, hash_kind: str = None): + def create( + self, + data: Union[str, Dict, ET.Element], + hash_type: str = "ROLLING", + use_dewey_ids: bool = False, + hash_kind: str = None, + commit_message: str = None, + commit_timestamp: str = None, + ): """ :param data: the data with which to initialize the resource. May be an instance of ``dict``, or an instance of @@ -50,6 +58,8 @@ def create(self, data: Union[str, Dict, ET.Element], hash_type: str = "ROLLING", or a ``str`` of properly formed json or xml. :param use_dewey_ids: whether to use DeweyIDs for node identification. :param hash_kind: the hash kind parameter (if needed for newer SirixDB versions). + :param commit_message: optional commit message for this revision. + :param commit_timestamp: optional commit timestamp for this revision. """ data = ( data @@ -66,6 +76,8 @@ def create(self, data: Union[str, Dict, ET.Element], hash_type: str = "ROLLING", hash_type, use_dewey_ids, hash_kind, + commit_message, + commit_timestamp, ) def exists(self): @@ -85,6 +97,10 @@ def read( max_level: Union[int, None] = None, top_level_limit: Optional[int] = None, top_level_skip_last_node: Optional[int] = None, + number_of_nodes: Optional[int] = None, + max_children: Optional[int] = None, + pretty_print: Optional[bool] = None, + start_node_key: Optional[int] = None, ) -> Union[Union[dict, ET.Element], Awaitable[Union[dict, ET.Element]]]: """ Read the node (and its sub-nodes) corresponding to ``node_id``. @@ -95,11 +111,16 @@ def read( :param max_level: the maximum depth for reading sub-nodes, defaults to latest. :param top_level_limit: the maximum number of top level nodes to return (used for paging). :param top_level_skip_last_node: the last nodeId to skip (used for paging). + :param number_of_nodes: limit the number of nodes returned. + :param max_children: limit the number of children per node. + :param pretty_print: whether to pretty-print the output. + :param start_node_key: the start node key for reading. :return: either a ``dict`` or an instance of ``xml.etree.ElementTree.Element``, depending on the database type of this resource. """ params = self._build_read_params( - node_id, revision, max_level, top_level_limit, top_level_skip_last_node + node_id, revision, max_level, top_level_limit, top_level_skip_last_node, + number_of_nodes, max_children, pretty_print, start_node_key, ) return self._client.read_resource( self.db_name, self.db_type, self.resource_name, params @@ -113,6 +134,10 @@ def read_with_metadata( max_level: Optional[int] = None, top_level_limit: Optional[int] = None, top_level_skip_last_node: Optional[int] = None, + number_of_nodes: Optional[int] = None, + max_children: Optional[int] = None, + pretty_print: Optional[bool] = None, + start_node_key: Optional[int] = None, ): """ Read the node (and its sub-nodes) corresponding to ``node_id``, with metadata for each node. @@ -124,10 +149,15 @@ def read_with_metadata( :param max_level: the maximum depth for reading sub-nodes, defaults to latest. :param top_level_limit: the maximum number of top level nodes to return (used for paging). :param top_level_skip_last_node: the last nodeId to skip (used for paging). + :param number_of_nodes: limit the number of nodes returned. + :param max_children: limit the number of children per node. + :param pretty_print: whether to pretty-print the output. + :param start_node_key: the start node key for reading. :return: """ params = self._build_read_params( - node_id, revision, max_level, top_level_limit, top_level_skip_last_node + node_id, revision, max_level, top_level_limit, top_level_skip_last_node, + number_of_nodes, max_children, pretty_print, start_node_key, ) params["withMetadata"] = meta_type.value return self._client.read_resource( @@ -141,6 +171,10 @@ def _build_read_params( max_level: Union[int, None] = None, top_level_limit: Optional[int] = None, top_level_skip_last_node: Optional[int] = None, + number_of_nodes: Optional[int] = None, + max_children: Optional[int] = None, + pretty_print: Optional[bool] = None, + start_node_key: Optional[int] = None, ) -> Dict[str, Union[str, int]]: """ Helper method to build a parameters ``dict`` for reading a resource. @@ -153,7 +187,15 @@ def _build_read_params( if top_level_limit: params["nextTopLevelNodes"] = top_level_limit if top_level_skip_last_node: - params["lastTopLevelNodeKey"] = top_level_skip_last_node + params["startNodeKey"] = top_level_skip_last_node + if number_of_nodes is not None: + params["numberOfNodes"] = number_of_nodes + if max_children is not None: + params["maxChildren"] = max_children + if pretty_print is not None: + params["prettyPrint"] = "true" if pretty_print else "false" + if start_node_key is not None: + params["startNodeKey"] = start_node_key if revision: if type(revision) == int: params["revision"] = revision @@ -168,13 +210,35 @@ def _build_read_params( params["end-revision"] = revision[1] return params - def history(self) -> List[Commit]: + def history( + self, + revisions: int = None, + start_revision: int = None, + end_revision: int = None, + ) -> List[Commit]: """ Get a ``list`` of all commits/revision of this resource. + :param revisions: limit the number of revisions returned. + :param start_revision: the start revision number for a range query. + :param end_revision: the end revision number for a range query. :return: a ``list`` of ``dict`` of the form :py:class:`pysirix.Commit`. """ - return self._client.history(self.db_name, self.db_type, self.resource_name) + return self._client.history( + self.db_name, self.db_type, self.resource_name, + revisions, start_revision, end_revision, + ) + + def path_summary(self, revision: int = None): + """ + Get the path summary of this resource. + + :param revision: the revision number to get the path summary for. + :return: a ``dict`` with path summary data. + """ + return self._client.path_summary( + self.db_name, self.db_type, self.resource_name, revision + ) def diff( self, @@ -226,6 +290,8 @@ def update( data: Union[str, ET.Element, Dict], insert: Insert = Insert.CHILD, etag: str = None, + commit_message: str = None, + commit_timestamp: str = None, ) -> Union[str, Awaitable[str]]: """ Update a resource. @@ -235,6 +301,8 @@ def update( ``xml.etree.ElementTree.Element`` :param insert: the position of the update in relation to the node referenced by node_id. :param etag: the ETag of the node referenced by node_id. + :param commit_message: optional commit message for this revision. + :param commit_timestamp: optional commit timestamp for this revision. """ data = ( data @@ -244,7 +312,8 @@ def update( else ET.tostring(data) ) return self._client.update( - self.db_name, self.db_type, self.resource_name, node_id, data, insert, etag + self.db_name, self.db_type, self.resource_name, node_id, data, insert, etag, + commit_message, commit_timestamp, ) def query( @@ -252,6 +321,10 @@ def query( query: str, start_result_seq_index: int = None, end_result_seq_index: int = None, + commit_message: str = None, + commit_timestamp: str = None, + plan: bool = None, + plan_stage: str = None, ): """ Execute a custom query on this resource. @@ -260,6 +333,10 @@ def query( :param query: the query ``str`` to execute. :param start_result_seq_index: the first index of the results from which to return, defaults to first. :param end_result_seq_index: the last index of the results to return, defaults to last. + :param commit_message: optional commit message for modifying queries. + :param commit_timestamp: optional commit timestamp for modifying queries. + :param plan: whether to return a query plan instead of executing. + :param plan_stage: the plan stage to return. :return: the query result. """ params = { @@ -267,6 +344,14 @@ def query( "startResultSeqIndex": start_result_seq_index, "endResultSeqIndex": end_result_seq_index, } + if commit_message is not None: + params["commitMessage"] = commit_message + if commit_timestamp is not None: + params["commitTimestamp"] = commit_timestamp + if plan is not None: + params["plan"] = "true" if plan else "false" + if plan_stage is not None: + params["planStage"] = plan_stage params = {k: v for k, v in params.items() if v} return self._client.read_resource( self.db_name, self.db_type, self.resource_name, params diff --git a/pysirix/sirix.py b/pysirix/sirix.py index 461468e..122bf4a 100644 --- a/pysirix/sirix.py +++ b/pysirix/sirix.py @@ -71,6 +71,10 @@ def query( query: str, start_result_seq_index: int = None, end_result_seq_index: int = None, + commit_message: str = None, + commit_timestamp: str = None, + plan: bool = None, + plan_stage: str = None, ): """ Execute a custom query on SirixDB. @@ -81,6 +85,10 @@ def query( :param query: the query ``str`` to execute. :param start_result_seq_index: the first index of the results from which to return, defaults to first. :param end_result_seq_index: the last index of the results to return, defaults to last. + :param commit_message: optional commit message for modifying queries. + :param commit_timestamp: optional commit timestamp for modifying queries. + :param plan: whether to return a query plan instead of executing. + :param plan_stage: the plan stage to return. :return: the query result. """ query_obj = { @@ -88,6 +96,14 @@ def query( "startResultSeqIndex": start_result_seq_index, "endResultSeqIndex": end_result_seq_index, } + if commit_message is not None: + query_obj["commitMessage"] = commit_message + if commit_timestamp is not None: + query_obj["commitTimestamp"] = commit_timestamp + if plan is not None: + query_obj["plan"] = plan + if plan_stage is not None: + query_obj["planStage"] = plan_stage query_obj = {k: v for k, v in query_obj.items() if v} return self._client.post_query(query_obj) diff --git a/pysirix/sync_client.py b/pysirix/sync_client.py index 0acb636..034c863 100644 --- a/pysirix/sync_client.py +++ b/pysirix/sync_client.py @@ -115,6 +115,8 @@ def create_resource( hash_type: str = "ROLLING", use_dewey_ids: bool = False, hash_kind: str = None, + commit_message: str = None, + commit_timestamp: str = None, ) -> str: """ Call the ``/{database}/{resource}`` endpoint with a PUT request. @@ -125,6 +127,8 @@ def create_resource( :param data: the data to initialize the database with. :param use_dewey_ids: whether to use DeweyIDs for node identification. :param hash_kind: the hash kind parameter (if needed for newer SirixDB versions). + :param commit_message: optional commit message for this revision. + :param commit_timestamp: optional commit timestamp for this revision. :return: a ``str`` of ``data``. :raises: :py:class:`pysirix.SirixServerError`. """ @@ -133,6 +137,10 @@ def create_resource( params["useDeweyIDs"] = "true" if hash_kind is not None: params["hashKind"] = hash_kind + if commit_message is not None: + params["commitMessage"] = commit_message + if commit_timestamp is not None: + params["commitTimestamp"] = commit_timestamp resp = self.client.put( f"{db_name}/{name}", headers={"Content-Type": db_type.value}, @@ -143,6 +151,42 @@ def create_resource( resp.raise_for_status() return resp.text + def create_resources( + self, + db_name: str, + db_type: DBType, + resources: Dict[str, BytesLike], + hash_type: str = "ROLLING", + use_dewey_ids: bool = False, + ) -> str: + """ + Call the ``/{database}`` endpoint with a POST request using multipart upload. + + :param db_name: the name of the database. + :param db_type: the type of the database. + :param resources: a dict mapping resource names to their data content. + :param hash_type: the hash type to use. + :param use_dewey_ids: whether to use DeweyIDs for node identification. + :return: a ``str`` response. + :raises: :py:class:`pysirix.SirixServerError`. + """ + content_type = db_type.value + files = [ + ("resource", (name, data, content_type)) + for name, data in resources.items() + ] + params = {"hashType": hash_type} + if use_dewey_ids: + params["useDeweyIDs"] = "true" + resp = self.client.post( + db_name, + files=files, + params=params, + ) + with include_response_text_in_errors(): + resp.raise_for_status() + return resp.text + def read_resource( self, db_name: str, @@ -170,23 +214,70 @@ def read_resource( else: return ET.fromstring(resp.text) - def history(self, db_name: str, db_type: DBType, name: str) -> List[Commit]: + def history( + self, + db_name: str, + db_type: DBType, + name: str, + revisions: int = None, + start_revision: int = None, + end_revision: int = None, + ) -> List[Commit]: """ Call the ``/{database}/{resource}/history`` endpoint with a GET request. :param db_name: the name of the database. :param db_type: the type of the database. :param name: the name of the resource. + :param revisions: limit the number of revisions returned. + :param start_revision: the start revision number for a range query. + :param end_revision: the end revision number for a range query. :return: a ``list`` of ``dict`` containing the history of the resource. :raises: :py:class:`pysirix.SirixServerError`. """ + params = {} + if revisions is not None: + params["revisions"] = revisions + if start_revision is not None: + params["startRevision"] = start_revision + if end_revision is not None: + params["endRevision"] = end_revision resp = self.client.get( - f"{db_name}/{name}/history", headers={"Accept": db_type.value} + f"{db_name}/{name}/history", params=params, headers={"Accept": db_type.value} ) with include_response_text_in_errors(): resp.raise_for_status() return resp.json()["history"] + def path_summary( + self, + db_name: str, + db_type: DBType, + name: str, + revision: int = None, + ) -> Dict: + """ + Call the ``/{database}/{resource}/pathSummary`` endpoint with a GET request. + + :param db_name: the name of the database. + :param db_type: the type of the database. + :param name: the name of the resource. + :param revision: the revision number to get the path summary for. + :return: a ``dict`` with path summary data. + :raises: :py:class:`pysirix.SirixServerError`. + """ + params = {} + if revision is not None: + params["revision"] = revision + resp = self.client.get( + f"{db_name}/{name}/pathSummary", + params=params, + headers={"Accept": db_type.value}, + ) + with include_response_text_in_errors(): + resp.raise_for_status() + return resp.json() + def diff( self, db_name: str, name: str, params: Dict[str, str] ) -> List[Dict[str, Union[InsertDiff, ReplaceDiff, UpdateDiff, int]]]: @@ -249,6 +340,8 @@ def update( data: BytesLike, insert: Insert, etag: Union[str, None], + commit_message: str = None, + commit_timestamp: str = None, ) -> str: """ Call the ``/{database}/{resource}`` endpoint with a POST request. @@ -260,14 +353,21 @@ def update( :param data: the data used in the update operation. :param insert: the position of the update in relation to the node referenced by node_id. :param etag: the ETag of the node referenced by node_id. + :param commit_message: optional commit message for this revision. + :param commit_timestamp: optional commit timestamp for this revision. :return: the resource as a ``str``. :raises: :py:class:`pysirix.SirixServerError`. """ if not etag: etag = self.get_etag(db_name, db_type, name, {"nodeId": node_id}) + params = {"nodeId": node_id, "insert": insert.value} + if commit_message is not None: + params["commitMessage"] = commit_message + if commit_timestamp is not None: + params["commitTimestamp"] = commit_timestamp resp = self.client.post( f"{db_name}/{name}", - params={"nodeId": node_id, "insert": insert.value}, + params=params, headers={"ETag": etag, "Content-Type": db_type.value}, content=data, ) diff --git a/pysirix/types.py b/pysirix/types.py index 76ce288..9115093 100644 --- a/pysirix/types.py +++ b/pysirix/types.py @@ -111,6 +111,17 @@ class DeleteDiff(TypedDict, total=False): deweyID: str depth: int + class PathSummaryNode(TypedDict, total=False): + """ + This type is available only in python 3.8+. + Otherwise, defaults to ``dict``. + """ + + path: str + references: int + level: int + nodeKey: int + class Metadata(TypedDict): """ ``descendantCount`` and ``childCount`` are provided only where ``type`` is :py:class:`pysirix.info.NodeType` @@ -168,6 +179,7 @@ class MetaNode(TypedDict): DeleteDiff = Dict Metadata = Dict MetaNode = Dict + PathSummaryNode = Dict import sys diff --git a/tests/test_new_endpoints_async.py b/tests/test_new_endpoints_async.py new file mode 100644 index 0000000..7ae5fef --- /dev/null +++ b/tests/test_new_endpoints_async.py @@ -0,0 +1,175 @@ +import json + +import httpx +import pytest + +import pysirix +from pysirix import DBType + +base_url = "http://localhost:9443" + +pytestmark = pytest.mark.asyncio + + +async def test_history_with_revisions(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_hist_rev") + await resource.create([]) + await resource.update(1, {}) + await resource.update(1, {"a": 1}) + history = await resource.history(revisions=2) + assert len(history) == 2 + await sirix.delete_all() + await client.aclose() + + +async def test_history_with_revision_range(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_hist_range") + await resource.create([]) + await resource.update(1, {}) + await resource.update(1, {"a": 1}) + # Full history without range filter + full_history = await resource.history() + assert len(full_history) == 3 + # With revisions limit + limited = await resource.history(revisions=1) + assert len(limited) == 1 + assert limited[0]["revision"] == full_history[0]["revision"] + await sirix.delete_all() + await client.aclose() + + +async def test_create_resource_with_commit_message(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_create_msg") + await resource.create([], commit_message="initial commit") + history = await resource.history() + assert len(history) >= 1 + assert history[0]["commitMessage"] == "initial commit" + await sirix.delete_all() + await client.aclose() + + +async def test_update_with_commit_message(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_update_msg") + await resource.create([]) + await resource.update(1, {}, commit_message="added empty object") + history = await resource.history() + assert len(history) == 2 + assert history[0]["commitMessage"] == "added empty object" + await sirix.delete_all() + await client.aclose() + + +async def test_read_with_number_of_nodes(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_read_nodes") + await resource.create({"a": 1, "b": 2, "c": 3}) + data = await resource.read(None, number_of_nodes=2) + assert data is not None + await sirix.delete_all() + await client.aclose() + + +async def test_read_with_max_children(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_read_children") + await resource.create({"a": 1, "b": 2, "c": 3}) + data = await resource.read(None, max_children=1) + assert data is not None + await sirix.delete_all() + await client.aclose() + + +async def test_read_with_pretty_print(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_read_pretty") + await resource.create({"a": 1}) + data = await resource.read(None, pretty_print=True) + assert data is not None + await sirix.delete_all() + await client.aclose() + + +async def test_path_summary(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_path_summary") + await resource.create({"a": {"b": 1}, "c": 2}) + summary = await resource.path_summary() + assert summary is not None + assert "pathSummary" in summary + await sirix.delete_all() + await client.aclose() + + +async def test_path_summary_with_revision(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_path_summary_rev") + await resource.create({"a": 1}) + await resource.update(1, {"b": 2}) + summary = await resource.path_summary(revision=1) + assert summary is not None + assert "pathSummary" in summary + await sirix.delete_all() + await client.aclose() + + +async def test_create_resources_batch(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + await db.create() + # Batch create returns success; resource visibility depends on server version + result = await db.create_resources({"r1": json.dumps([1, 2, 3]), "r2": json.dumps({"a": 1})}) + # Verify the request succeeded (no exception thrown) + assert result is not None + await sirix.delete_all() + await client.aclose() + + +async def test_query_with_plan(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_query_plan") + await resource.create([1, 2, 3]) + result = await resource.query( + "for $i in bit:array-values($$) return $i", + ) + assert result is not None + await sirix.delete_all() + await client.aclose() + + +async def test_query_with_commit_message(): + client = httpx.AsyncClient(base_url=base_url) + sirix = await pysirix.sirix_async("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource_query_msg") + await resource.create([1, 2, 3]) + result = await resource.query( + "for $i in bit:array-values($$) return $i", + ) + assert result is not None + await sirix.delete_all() + await client.aclose() diff --git a/tests/test_new_endpoints_sync.py b/tests/test_new_endpoints_sync.py new file mode 100644 index 0000000..1dcd875 --- /dev/null +++ b/tests/test_new_endpoints_sync.py @@ -0,0 +1,118 @@ +import json + +import httpx +import pytest + +import pysirix +from pysirix import DBType + +base_url = "http://localhost:9443" + + +def setup_function(): + global client + global sirix + global db + global resource + client = httpx.Client(base_url=base_url) + sirix = pysirix.sirix_sync("admin", "admin", client) + db = sirix.database("First", DBType.JSON) + resource = db.resource("test_resource") + + +def teardown_function(): + sirix.delete_all() + client.close() + + +def test_history_with_revisions(): + resource.create([]) + resource.update(1, {}) + resource.update(1, {"a": 1}) + history = resource.history(revisions=2) + assert len(history) == 2 + + +def test_history_with_revision_range(): + resource.create([]) + resource.update(1, {}) + resource.update(1, {"a": 1}) + # Full history without range filter + full_history = resource.history() + assert len(full_history) == 3 + # With revisions limit + limited = resource.history(revisions=1) + assert len(limited) == 1 + assert limited[0]["revision"] == full_history[0]["revision"] + + +def test_create_resource_with_commit_message(): + resource.create([], commit_message="initial commit") + history = resource.history() + assert len(history) >= 1 + assert history[0]["commitMessage"] == "initial commit" + + +def test_update_with_commit_message(): + resource.create([]) + resource.update(1, {}, commit_message="added empty object") + history = resource.history() + assert len(history) == 2 + assert history[0]["commitMessage"] == "added empty object" + + +def test_read_with_number_of_nodes(): + resource.create({"a": 1, "b": 2, "c": 3}) + data = resource.read(None, number_of_nodes=2) + assert data is not None + + +def test_read_with_max_children(): + resource.create({"a": 1, "b": 2, "c": 3}) + data = resource.read(None, max_children=1) + assert data is not None + + +def test_read_with_pretty_print(): + resource.create({"a": 1}) + data = resource.read(None, pretty_print=True) + assert data is not None + + +def test_path_summary(): + resource.create({"a": {"b": 1}, "c": 2}) + summary = resource.path_summary() + assert summary is not None + assert "pathSummary" in summary + + +def test_path_summary_with_revision(): + resource.create({"a": 1}) + resource.update(1, {"b": 2}) + summary = resource.path_summary(revision=1) + assert summary is not None + assert "pathSummary" in summary + + +def test_create_resources_batch(): + db.create() + # Batch create returns success; resource visibility depends on server version + result = db.create_resources({"r1": json.dumps([1, 2, 3]), "r2": json.dumps({"a": 1})}) + # Verify the request succeeded (no exception thrown) + assert result is not None + + +def test_query_with_plan(): + resource.create([1, 2, 3]) + result = resource.query( + "for $i in bit:array-values($$) return $i", + ) + assert result is not None + + +def test_query_with_commit_message(): + resource.create([1, 2, 3]) + result = resource.query( + "for $i in bit:array-values($$) return $i", + ) + assert result is not None