From d1ec8db7bc77fa43b2200e6dcf75aae81780e059 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 10:56:59 +0300 Subject: [PATCH 1/4] feat(database): Hub record methods, trees, list settings, embed, bundle; Api find_own and merged tree Adds the 25 database routes the SDK did not call yet, sync and async: - Hub records: find_records, find_one_record, insert_record, insert_many_records, update_one_record, update_many_records, replace_record, delete_record, delete_many_records, count_records, distinct_record_values, aggregate_records, execute_records_aggregate, change_record_responsibility, seed_collection_records, get_collection_indexes. - Hub schemas: apply_database_schema_bundle, update_database_schema_embed, get/update_database_schema_list_settings. - Hub taxonomies: get_database_taxonomy_tree, get_database_taxonomy_term_tree, get_database_merged_term_tree. - Api: find_own, find_merged_term_tree. Untyped like the rest of the SDK: values pass through **request. Co-Authored-By: Claude Opus 5.5 --- src/norbix_python/api/database.py | 52 +++ src/norbix_python/hub/database.py | 598 ++++++++++++++++++++++++++++++ 2 files changed, 650 insertions(+) diff --git a/src/norbix_python/api/database.py b/src/norbix_python/api/database.py index 574ce7e..ab1685f 100644 --- a/src/norbix_python/api/database.py +++ b/src/norbix_python/api/database.py @@ -269,6 +269,32 @@ def update_one(self, collection_name: str, id: str, *, timeout: float | None = N bearer_token=bearer_token, ) + def find_merged_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{taxonomyName}/merged-tree""" + return self._transport.send( + target="api", + path="/{version}/database/taxonomies/{taxonomyName}/merged-tree", + method="GET", + path_params={"taxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def find_own(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/own""" + return self._transport.send( + target="api", + path="/{version}/database/collections/{collectionName}/own", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + class AsyncDatabaseModule: def __init__(self, transport: AsyncTransport) -> None: @@ -533,3 +559,29 @@ async def update_one(self, collection_name: str, id: str, *, timeout: float | No timeout=timeout, bearer_token=bearer_token, ) + + async def find_merged_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{taxonomyName}/merged-tree""" + return await self._transport.send( + target="api", + path="/{version}/database/taxonomies/{taxonomyName}/merged-tree", + method="GET", + path_params={"taxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def find_own(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/own""" + return await self._transport.send( + target="api", + path="/{version}/database/collections/{collectionName}/own", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) diff --git a/src/norbix_python/hub/database.py b/src/norbix_python/hub/database.py index e51ca04..4317565 100644 --- a/src/norbix_python/hub/database.py +++ b/src/norbix_python/hub/database.py @@ -581,6 +581,305 @@ def reveal_managed_flex_connection_string(self, id: str, *, timeout: float | Non bearer_token=bearer_token, ) + def get_database_taxonomy_tree(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/tree""" + return self._transport.send( + target="hub", + path="/{version}/database/taxonomies/tree", + method="GET", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def get_database_merged_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{TaxonomyName}/merged-tree""" + return self._transport.send( + target="hub", + path="/{version}/database/taxonomies/{TaxonomyName}/merged-tree", + method="GET", + path_params={"TaxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def get_database_taxonomy_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{TaxonomyName}/terms/tree""" + return self._transport.send( + target="hub", + path="/{version}/database/taxonomies/{TaxonomyName}/terms/tree", + method="GET", + path_params={"TaxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def apply_database_schema_bundle(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/schemas/apply-bundle""" + return self._transport.send( + target="hub", + path="/{version}/database/schemas/apply-bundle", + method="POST", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def get_database_schema_list_settings(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/schemas/{Id}/list-settings""" + return self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/list-settings", + method="GET", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def update_database_schema_list_settings(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/schemas/{Id}/list-settings""" + return self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/list-settings", + method="PUT", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def update_database_schema_embed(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/schemas/{Id}/embed""" + return self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/embed", + method="PUT", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def aggregate_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/aggregate""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/aggregate", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def change_record_responsibility(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}/responsibility""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}/responsibility", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def count_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/count""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/count", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def delete_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """DELETE /{version}/database/collections/{collectionName}/many""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="DELETE", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def delete_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """DELETE /{version}/database/collections/{collectionName}/{id}""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="DELETE", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def distinct_record_values(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/distinct""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/distinct", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def execute_records_aggregate(self, collection_name: str, aggregate_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/aggregates/{aggregateId}/execute""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/aggregates/{aggregateId}/execute", + method="POST", + path_params={"collectionName": collection_name, "aggregateId": aggregate_id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def find_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def find_one_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/{id}""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="GET", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def get_collection_indexes(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/indexes""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/indexes", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def insert_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/many""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def insert_record(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def replace_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}/replace""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}/replace", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def seed_collection_records(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/seed""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/seed", + method="POST", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def update_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/many""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="PUT", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + def update_one_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}""" + return self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + class AsyncDatabaseModule: def __init__(self, transport: AsyncTransport) -> None: @@ -1157,3 +1456,302 @@ async def reveal_managed_flex_connection_string(self, id: str, *, timeout: float timeout=timeout, bearer_token=bearer_token, ) + + async def get_database_taxonomy_tree(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/tree""" + return await self._transport.send( + target="hub", + path="/{version}/database/taxonomies/tree", + method="GET", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def get_database_merged_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{TaxonomyName}/merged-tree""" + return await self._transport.send( + target="hub", + path="/{version}/database/taxonomies/{TaxonomyName}/merged-tree", + method="GET", + path_params={"TaxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def get_database_taxonomy_term_tree(self, taxonomy_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/taxonomies/{TaxonomyName}/terms/tree""" + return await self._transport.send( + target="hub", + path="/{version}/database/taxonomies/{TaxonomyName}/terms/tree", + method="GET", + path_params={"TaxonomyName": taxonomy_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def apply_database_schema_bundle(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/schemas/apply-bundle""" + return await self._transport.send( + target="hub", + path="/{version}/database/schemas/apply-bundle", + method="POST", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def get_database_schema_list_settings(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/schemas/{Id}/list-settings""" + return await self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/list-settings", + method="GET", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def update_database_schema_list_settings(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/schemas/{Id}/list-settings""" + return await self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/list-settings", + method="PUT", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def update_database_schema_embed(self, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/schemas/{Id}/embed""" + return await self._transport.send( + target="hub", + path="/{version}/database/schemas/{Id}/embed", + method="PUT", + path_params={"Id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def aggregate_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/aggregate""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/aggregate", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def change_record_responsibility(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}/responsibility""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}/responsibility", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def count_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/count""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/count", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def delete_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """DELETE /{version}/database/collections/{collectionName}/many""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="DELETE", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def delete_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """DELETE /{version}/database/collections/{collectionName}/{id}""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="DELETE", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def distinct_record_values(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/distinct""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/distinct", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def execute_records_aggregate(self, collection_name: str, aggregate_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/aggregates/{aggregateId}/execute""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/aggregates/{aggregateId}/execute", + method="POST", + path_params={"collectionName": collection_name, "aggregateId": aggregate_id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def find_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def find_one_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/{id}""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="GET", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def get_collection_indexes(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/database/collections/{collectionName}/indexes""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/indexes", + method="GET", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def insert_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}/many""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def insert_record(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/{collectionName}""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}", + method="POST", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def replace_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}/replace""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}/replace", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def seed_collection_records(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/database/collections/seed""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/seed", + method="POST", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def update_many_records(self, collection_name: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/many""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/many", + method="PUT", + path_params={"collectionName": collection_name}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + async def update_one_record(self, collection_name: str, id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """PUT /{version}/database/collections/{collectionName}/{id}""" + return await self._transport.send( + target="hub", + path="/{version}/database/collections/{collectionName}/{id}", + method="PUT", + path_params={"collectionName": collection_name, "id": id}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) From 9e42680fe421e0d8ee0d73b9dda0d294c9d819f2 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 10:56:59 +0300 Subject: [PATCH 2/4] test(database): resolved route, query and body checks for the new database methods Each new Hub method is checked against its full gateway path and verb; a GET sends query values, a write sends a JSON body, and the async module has the same methods. Api find_own and find_merged_term_tree get the same checks. Co-Authored-By: Claude Opus 5.5 --- tests/api/test_database.py | 60 +++++++++++++- tests/hub/test_database.py | 156 ++++++++++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 2 deletions(-) diff --git a/tests/api/test_database.py b/tests/api/test_database.py index 92d4881..76654de 100644 --- a/tests/api/test_database.py +++ b/tests/api/test_database.py @@ -1,6 +1,11 @@ from __future__ import annotations -from norbix_python import Norbix, NorbixError +import asyncio +from urllib.parse import parse_qs, urlparse + +import httpx + +from norbix_python import AsyncNorbix, Norbix, NorbixError from ..helpers import make_client @@ -152,3 +157,56 @@ def test_api_database_update_one_request_shape() -> None: assert transport.last_request['method'] == 'PUT' assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') + + +# --- Database audit (2026-10): find_own and find_merged_term_tree --- + + +def test_api_database_find_own_hits_the_own_route_with_query_values() -> None: + client, transport = make_client() + client.api.database.find_own("products", pageSize=10) + + assert transport.last_request is not None + assert transport.last_request["method"] == "GET" + url = urlparse(transport.last_request["url"]) + assert url.netloc == "api.norbix.ai" + assert url.path == "/v2/database/collections/products/own" + assert parse_qs(url.query) == {"pageSize": ["10"]} + + +def test_api_database_find_merged_term_tree_hits_the_merged_tree_route() -> None: + client, transport = make_client() + client.api.database.find_merged_term_tree("services") + + assert transport.last_request is not None + assert transport.last_request["method"] == "GET" + url = urlparse(transport.last_request["url"]) + assert url.netloc == "api.norbix.ai" + assert url.path == "/v2/database/taxonomies/services/merged-tree" + + +def test_api_database_audit_methods_exist_on_the_async_module() -> None: + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={}) + + async def run() -> None: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + await client.api.database.find_own("products") + await client.api.database.find_merged_term_tree("services") + finally: + await client.aclose() + + asyncio.run(run()) + + assert [(r.method, urlparse(str(r.url)).path) for r in seen] == [ + ("GET", "/v2/database/collections/products/own"), + ("GET", "/v2/database/taxonomies/services/merged-tree"), + ] diff --git a/tests/hub/test_database.py b/tests/hub/test_database.py index 3d4b11e..c824a70 100644 --- a/tests/hub/test_database.py +++ b/tests/hub/test_database.py @@ -1,6 +1,16 @@ from __future__ import annotations -from norbix_python import Norbix, NorbixError +import asyncio +import inspect +import json +from collections.abc import Callable +from typing import Any +from urllib.parse import parse_qs, urlparse + +import httpx +import pytest + +from norbix_python import AsyncNorbix, Norbix, NorbixError from ..helpers import make_client @@ -369,3 +379,147 @@ def test_hub_database_reveal_managed_flex_connection_string_request_shape() -> N assert transport.last_request is not None assert transport.last_request['url'].startswith('https://') + +# --- Database audit (2026-10): Hub records, trees, list settings, embed, bundle --- +# +# The generated tests above check only the verb and the https scheme. The cases +# below check the fully resolved path of every method added in the database +# audit, that query values reach a GET and a JSON body reaches a write, and +# that the async module has the same methods. Every call goes to a capture +# transport; nothing leaves the process. + + +DB = "/v2/database" +COLL = "products" +REC = "rec_1" +SCHEMA = "sch_1" +TAX = "services" +AGG = "agg_1" + +DbCase = tuple[str, str, str, Callable[[Any], Any]] + +HUB_DB_AUDIT_CASES: list[DbCase] = [ + # taxonomies + ("get_database_taxonomy_tree", "GET", f"{DB}/taxonomies/tree", lambda m: m.get_database_taxonomy_tree()), + ("get_database_merged_term_tree", "GET", f"{DB}/taxonomies/{TAX}/merged-tree", lambda m: m.get_database_merged_term_tree(TAX)), + ("get_database_taxonomy_term_tree", "GET", f"{DB}/taxonomies/{TAX}/terms/tree", lambda m: m.get_database_taxonomy_term_tree(TAX)), + # schemas + ("apply_database_schema_bundle", "POST", f"{DB}/schemas/apply-bundle", lambda m: m.apply_database_schema_bundle()), + ("get_database_schema_list_settings", "GET", f"{DB}/schemas/{SCHEMA}/list-settings", lambda m: m.get_database_schema_list_settings(SCHEMA)), + ("update_database_schema_list_settings", "PUT", f"{DB}/schemas/{SCHEMA}/list-settings", lambda m: m.update_database_schema_list_settings(SCHEMA)), + ("update_database_schema_embed", "PUT", f"{DB}/schemas/{SCHEMA}/embed", lambda m: m.update_database_schema_embed(SCHEMA)), + # collections / records + ("seed_collection_records", "POST", f"{DB}/collections/seed", lambda m: m.seed_collection_records()), + ("find_records", "GET", f"{DB}/collections/{COLL}", lambda m: m.find_records(COLL)), + ("insert_record", "POST", f"{DB}/collections/{COLL}", lambda m: m.insert_record(COLL)), + ("find_one_record", "GET", f"{DB}/collections/{COLL}/{REC}", lambda m: m.find_one_record(COLL, REC)), + ("update_one_record", "PUT", f"{DB}/collections/{COLL}/{REC}", lambda m: m.update_one_record(COLL, REC)), + ("delete_record", "DELETE", f"{DB}/collections/{COLL}/{REC}", lambda m: m.delete_record(COLL, REC)), + ("replace_record", "PUT", f"{DB}/collections/{COLL}/{REC}/replace", lambda m: m.replace_record(COLL, REC)), + ("change_record_responsibility", "PUT", f"{DB}/collections/{COLL}/{REC}/responsibility", lambda m: m.change_record_responsibility(COLL, REC)), + ("insert_many_records", "POST", f"{DB}/collections/{COLL}/many", lambda m: m.insert_many_records(COLL)), + ("update_many_records", "PUT", f"{DB}/collections/{COLL}/many", lambda m: m.update_many_records(COLL)), + ("delete_many_records", "DELETE", f"{DB}/collections/{COLL}/many", lambda m: m.delete_many_records(COLL)), + ("count_records", "GET", f"{DB}/collections/{COLL}/count", lambda m: m.count_records(COLL)), + ("distinct_record_values", "GET", f"{DB}/collections/{COLL}/distinct", lambda m: m.distinct_record_values(COLL)), + ("get_collection_indexes", "GET", f"{DB}/collections/{COLL}/indexes", lambda m: m.get_collection_indexes(COLL)), + ("aggregate_records", "POST", f"{DB}/collections/{COLL}/aggregate", lambda m: m.aggregate_records(COLL)), + ("execute_records_aggregate", "POST", f"{DB}/collections/{COLL}/aggregates/{AGG}/execute", lambda m: m.execute_records_aggregate(COLL, AGG)), +] + + +@pytest.mark.parametrize( + ("name", "verb", "path", "call"), + HUB_DB_AUDIT_CASES, + ids=[case[0] for case in HUB_DB_AUDIT_CASES], +) +def test_hub_database_audit_method_hits_the_gateway_route( + name: str, verb: str, path: str, call: Callable[[Any], Any] +) -> None: + client, transport = make_client(account_id=None) + call(client.hub.database) + + assert transport.last_request is not None + assert transport.last_request["method"] == verb + url = urlparse(transport.last_request["url"]) + assert url.netloc == "hub.norbix.ai" + assert url.path == path, f"{name}: got {transport.last_request['url']}, expected the path {path}" + + +def test_hub_database_audit_surface_size() -> None: + """23 Hub database routes were missing before the audit; all of them are covered here.""" + assert len(HUB_DB_AUDIT_CASES) == 23 + assert len({case[0] for case in HUB_DB_AUDIT_CASES}) == 23 + + +def test_hub_database_find_records_sends_query_values() -> None: + client, transport = make_client() + client.hub.database.find_records(COLL, filter='{"status":"active"}', pageSize=5, pageNumber=2) + + assert transport.last_request is not None + url = urlparse(transport.last_request["url"]) + assert url.path == f"{DB}/collections/{COLL}" + assert parse_qs(url.query) == { + "filter": ['{"status":"active"}'], + "pageSize": ["5"], + "pageNumber": ["2"], + } + assert transport.last_request["body"] == "" + + +def test_hub_database_insert_record_sends_the_document_as_json() -> None: + client, transport = make_client() + client.hub.database.insert_record(COLL, document={"title": "Lamp", "price": 12}) + + assert transport.last_request is not None + assert urlparse(transport.last_request["url"]).query == "" + assert json.loads(transport.last_request["body"]) == {"document": {"title": "Lamp", "price": 12}} + + +def test_hub_database_update_schema_list_settings_keeps_the_id_in_the_path_only() -> None: + client, transport = make_client() + client.hub.database.update_database_schema_list_settings(SCHEMA, columns=["title", "price"]) + + assert transport.last_request is not None + assert urlparse(transport.last_request["url"]).path == f"{DB}/schemas/{SCHEMA}/list-settings" + assert json.loads(transport.last_request["body"]) == {"columns": ["title", "price"]} + + +def test_hub_database_record_call_sends_auth_and_project_headers() -> None: + client, transport = make_client() + client.hub.database.count_records(COLL) + + assert transport.last_request is not None + headers = transport.last_request["headers"] + assert headers["authorization"] == "Bearer test-token" + assert headers["x-cm-projectid"] == "test-project" + + +def test_hub_database_audit_methods_exist_on_the_async_module() -> None: + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={}) + + async def run() -> None: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + module = client.hub.database + for name, _, _, _ in HUB_DB_AUDIT_CASES: + assert inspect.iscoroutinefunction(getattr(module, name)), name + await module.delete_record(COLL, REC) + await module.get_database_merged_term_tree(TAX) + finally: + await client.aclose() + + asyncio.run(run()) + + assert [(r.method, urlparse(str(r.url)).path) for r in seen] == [ + ("DELETE", f"{DB}/collections/{COLL}/{REC}"), + ("GET", f"{DB}/taxonomies/{TAX}/merged-tree"), + ] From 12dadd359757a446022971ba5d0ceced9f3ecf22 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 10:57:54 +0300 Subject: [PATCH 3/4] test(database): use the gateway's real value shapes for insert and list settings Document is an extended-JSON string and list settings go under "settings", as the gateway request types define them. Co-Authored-By: Claude Opus 5.5 --- tests/hub/test_database.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/hub/test_database.py b/tests/hub/test_database.py index c824a70..d90645c 100644 --- a/tests/hub/test_database.py +++ b/tests/hub/test_database.py @@ -467,22 +467,22 @@ def test_hub_database_find_records_sends_query_values() -> None: assert transport.last_request["body"] == "" -def test_hub_database_insert_record_sends_the_document_as_json() -> None: +def test_hub_database_insert_record_sends_the_document_in_the_json_body() -> None: client, transport = make_client() - client.hub.database.insert_record(COLL, document={"title": "Lamp", "price": 12}) + client.hub.database.insert_record(COLL, document='{"title":"Lamp","price":12}') assert transport.last_request is not None assert urlparse(transport.last_request["url"]).query == "" - assert json.loads(transport.last_request["body"]) == {"document": {"title": "Lamp", "price": 12}} + assert json.loads(transport.last_request["body"]) == {"document": '{"title":"Lamp","price":12}'} def test_hub_database_update_schema_list_settings_keeps_the_id_in_the_path_only() -> None: client, transport = make_client() - client.hub.database.update_database_schema_list_settings(SCHEMA, columns=["title", "price"]) + client.hub.database.update_database_schema_list_settings(SCHEMA, settings={"columns": ["title", "price"]}) assert transport.last_request is not None assert urlparse(transport.last_request["url"]).path == f"{DB}/schemas/{SCHEMA}/list-settings" - assert json.loads(transport.last_request["body"]) == {"columns": ["title", "price"]} + assert json.loads(transport.last_request["body"]) == {"settings": {"columns": ["title", "price"]}} def test_hub_database_record_call_sends_auth_and_project_headers() -> None: From ecdec5df4feec4d8975d275d64d01d286898c375 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Mon, 5 Oct 2026 10:57:54 +0300 Subject: [PATCH 4/4] docs(database): new Hub record, tree, list-settings, embed and bundle methods; Api find_own and merged tree The Hub table also gains the three flex integration methods that were in the SDK but missing from the page, and both index counts are updated. Co-Authored-By: Claude Opus 5.5 --- docs/api/_index.md | 2 +- docs/api/database.md | 22 +++++++++++++++++ docs/hub/_index.md | 2 +- docs/hub/database.md | 59 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/docs/api/_index.md b/docs/api/_index.md index 7a90248..bc9a9b7 100644 --- a/docs/api/_index.md +++ b/docs/api/_index.md @@ -3,7 +3,7 @@ | Module | Endpoints | | --- | ---: | | [`ai`](./ai.md) | 16 | -| [`database`](./database.md) | 18 | +| [`database`](./database.md) | 22 | | [`echo`](./echo.md) | 1 | | [`files`](./files.md) | 10 | | [`membership`](./membership.md) | 18 | diff --git a/docs/api/database.md b/docs/api/database.md index 7bbae72..9a0bd5b 100644 --- a/docs/api/database.md +++ b/docs/api/database.md @@ -24,6 +24,8 @@ Access with `norbix.api.database`. | `replace_one` | `PUT` | `/{version}/database/collections/{collectionName}/{id}/replace` | `project` | | `update_many` | `PUT` | `/{version}/database/collections/{collectionName}/many` | `project` | | `update_one` | `PUT` | `/{version}/database/collections/{collectionName}/{id}` | `project` | +| `find_merged_term_tree` | `GET` | `/{version}/database/taxonomies/{taxonomyName}/merged-tree` | `project` | +| `find_own` | `GET` | `/{version}/database/collections/{collectionName}/own` | `project` | ## Working with terms @@ -315,3 +317,23 @@ structure = norbix.api.database.find_taxonomy_tree(include_terms=True) Now each taxonomy node's `terms` holds that taxonomy's full term tree (same shape as `find_term_tree`) — *Countries* carries its countries, *Cities* carries its cities. > Every term-reading call also accepts an optional `database_integration_id` to target a non-default database. + +--- + +### Records the caller owns + +**Goal:** list only the records of `products` that the signed-in user is +responsible for. + +```python +mine = norbix.api.database.find_own("products", pageSize=20) +``` + +### A taxonomy's merged term tree + +**Goal:** get one tree across taxonomies: the terms of `services` are the +roots, and the terms of its child taxonomies nest under them. + +```python +tree = norbix.api.database.find_merged_term_tree("services") +``` diff --git a/docs/hub/_index.md b/docs/hub/_index.md index 54b054b..d92e757 100644 --- a/docs/hub/_index.md +++ b/docs/hub/_index.md @@ -4,7 +4,7 @@ | --- | ---: | | [`account`](./account.md) | 61 | | [`ai`](./ai.md) | 14 | -| [`database`](./database.md) | 41 | +| [`database`](./database.md) | 67 | | [`echo`](./echo.md) | 1 | | [`email`](./email.md) | 1 | | [`files`](./files.md) | 22 | diff --git a/docs/hub/database.md b/docs/hub/database.md index 87c2578..3d8bb95 100644 --- a/docs/hub/database.md +++ b/docs/hub/database.md @@ -45,3 +45,62 @@ Access with `norbix.hub.database`. | `get_database_aggregates` | `GET` | `/{version}/database/aggregates` | `project` | | `save_database_aggregate` | `POST` | `/{version}/database/aggregates` | `project` | | `test_database_aggregate` | `POST` | `/{version}/database/aggregates/test` | `project` | +| `get_allowed_flex_tiers` | `GET` | `/{version}/database/integrations/flex-tiers` | `project` | +| `test_database_integration` | `POST` | `/{version}/database/integrations/test` | `project` | +| `reveal_managed_flex_connection_string` | `GET` | `/{version}/database/integrations/{Id}/connection-string` | `project` | +| `get_database_taxonomy_tree` | `GET` | `/{version}/database/taxonomies/tree` | `project` | +| `get_database_merged_term_tree` | `GET` | `/{version}/database/taxonomies/{TaxonomyName}/merged-tree` | `project` | +| `get_database_taxonomy_term_tree` | `GET` | `/{version}/database/taxonomies/{TaxonomyName}/terms/tree` | `project` | +| `apply_database_schema_bundle` | `POST` | `/{version}/database/schemas/apply-bundle` | `project` | +| `get_database_schema_list_settings` | `GET` | `/{version}/database/schemas/{Id}/list-settings` | `project` | +| `update_database_schema_list_settings` | `PUT` | `/{version}/database/schemas/{Id}/list-settings` | `project` | +| `update_database_schema_embed` | `PUT` | `/{version}/database/schemas/{Id}/embed` | `project` | +| `aggregate_records` | `POST` | `/{version}/database/collections/{collectionName}/aggregate` | `project` | +| `change_record_responsibility` | `PUT` | `/{version}/database/collections/{collectionName}/{id}/responsibility` | `project` | +| `count_records` | `GET` | `/{version}/database/collections/{collectionName}/count` | `project` | +| `delete_many_records` | `DELETE` | `/{version}/database/collections/{collectionName}/many` | `project` | +| `delete_record` | `DELETE` | `/{version}/database/collections/{collectionName}/{id}` | `project` | +| `distinct_record_values` | `GET` | `/{version}/database/collections/{collectionName}/distinct` | `project` | +| `execute_records_aggregate` | `POST` | `/{version}/database/collections/{collectionName}/aggregates/{aggregateId}/execute` | `project` | +| `find_records` | `GET` | `/{version}/database/collections/{collectionName}` | `project` | +| `find_one_record` | `GET` | `/{version}/database/collections/{collectionName}/{id}` | `project` | +| `get_collection_indexes` | `GET` | `/{version}/database/collections/{collectionName}/indexes` | `project` | +| `insert_many_records` | `POST` | `/{version}/database/collections/{collectionName}/many` | `project` | +| `insert_record` | `POST` | `/{version}/database/collections/{collectionName}` | `project` | +| `replace_record` | `PUT` | `/{version}/database/collections/{collectionName}/{id}/replace` | `project` | +| `seed_collection_records` | `POST` | `/{version}/database/collections/seed` | `project` | +| `update_many_records` | `PUT` | `/{version}/database/collections/{collectionName}/many` | `project` | +| `update_one_record` | `PUT` | `/{version}/database/collections/{collectionName}/{id}` | `project` | + +## Working with records (Hub) + +The Hub record methods are the admin side of collections: they act on every +record in the project, not only the records the caller owns (for that, use +`norbix.api.database.find_own`). Values go in as keyword arguments, the same +way as every other method in this SDK: on a `GET` they become query values, on +a write they become the JSON body. Filters, documents and updates are MongoDB +extended-JSON **strings**. + +```python +products = norbix.hub.database.find_records("products", filter='{"status": "active"}', pageSize=20) +one = norbix.hub.database.find_one_record("products", "rec_1") +created = norbix.hub.database.insert_record("products", document='{"title": "Lamp", "price": 12}') +norbix.hub.database.update_one_record("products", "rec_1", update='{"$set": {"price": 10}}') +norbix.hub.database.delete_record("products", "rec_1") +total = norbix.hub.database.count_records("products", filter='{"status": "active"}') +indexes = norbix.hub.database.get_collection_indexes("products") +``` + +## Trees, list settings, embed and bundles + +- `get_database_taxonomy_tree()` — the taxonomy structure as a tree. +- `get_database_taxonomy_term_tree("services")` — one taxonomy's terms as a tree. +- `get_database_merged_term_tree("services")` — one tree across taxonomies: the + terms of `services` are the roots, and the terms of its child taxonomies nest + under them. +- `get_database_schema_list_settings(id)` / `update_database_schema_list_settings(id, settings={...})` + — how the record list shows a schema (the update replaces the whole settings object). +- `update_database_schema_embed(id, ...)` — the schema's embed settings. +- `apply_database_schema_bundle(...)` — apply a bundle of schemas in one call. + +The async client (`AsyncNorbix`) has the same methods; `await` them.