Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import logging

from gooddata_sdk._version import __version__
from gooddata_sdk.catalog.knowledge.model.knowledge_document import (
CatalogKnowledgeDocumentMetadata,
CatalogListKnowledgeDocumentsResponse,
CatalogPatchKnowledgeDocumentRequest,
)
from gooddata_sdk.catalog.appearance.entity_model.color_palette import (
CatalogColorPalette,
CatalogColorPaletteAttributes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# (C) 2024 GoodData Corporation
from gooddata_sdk.catalog.knowledge.model.knowledge_document import (
CatalogKnowledgeDocumentMetadata,
CatalogListKnowledgeDocumentsResponse,
CatalogPatchKnowledgeDocumentRequest,
)

__all__ = [
"CatalogKnowledgeDocumentMetadata",
"CatalogListKnowledgeDocumentsResponse",
"CatalogPatchKnowledgeDocumentRequest",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# (C) 2024 GoodData Corporation
from gooddata_sdk.catalog.knowledge.model.knowledge_document import (
CatalogKnowledgeDocumentMetadata,
CatalogListKnowledgeDocumentsResponse,
CatalogPatchKnowledgeDocumentRequest,
)

__all__ = [
"CatalogKnowledgeDocumentMetadata",
"CatalogListKnowledgeDocumentsResponse",
"CatalogPatchKnowledgeDocumentRequest",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# (C) 2024 GoodData Corporation
from __future__ import annotations

from typing import Any

import attrs
from gooddata_api_client.model.knowledge_document_metadata_dto import KnowledgeDocumentMetadataDto
from gooddata_api_client.model.list_knowledge_documents_response_dto import ListKnowledgeDocumentsResponseDto
from gooddata_api_client.model.patch_knowledge_document_request_dto import PatchKnowledgeDocumentRequestDto


@attrs.define(kw_only=True)
class CatalogKnowledgeDocumentMetadata:
filename: str
title: str | None = None
is_disabled: bool | None = None
scopes: list[str] | None = None

@classmethod
def from_api(cls, dto: KnowledgeDocumentMetadataDto) -> CatalogKnowledgeDocumentMetadata:
return cls(
filename=dto["filename"],
title=dto.get("title"),
is_disabled=dto.get("is_disabled"),
scopes=list(dto["scopes"]) if "scopes" in dto else None,
)

def as_api_model(self) -> PatchKnowledgeDocumentRequestDto:
kwargs: dict[str, Any] = {}
if self.title is not None:
kwargs["title"] = self.title
if self.is_disabled is not None:
kwargs["is_disabled"] = self.is_disabled
if self.scopes is not None:
kwargs["scopes"] = self.scopes
return PatchKnowledgeDocumentRequestDto(_check_type=False, **kwargs)


@attrs.define(kw_only=True)
class CatalogListKnowledgeDocumentsResponse:
documents: list[CatalogKnowledgeDocumentMetadata]
total_count: int | None = None
next_page_token: str | None = None

@classmethod
def from_api(cls, dto: ListKnowledgeDocumentsResponseDto) -> CatalogListKnowledgeDocumentsResponse:
documents = [CatalogKnowledgeDocumentMetadata.from_api(d) for d in dto["documents"]]
return cls(
documents=documents,
total_count=dto.get("total_count"),
next_page_token=dto.get("next_page_token"),
)


@attrs.define(kw_only=True)
class CatalogPatchKnowledgeDocumentRequest:
is_disabled: bool | None = None
title: str | None = None
scopes: list[str] | None = None

def as_api_model(self) -> PatchKnowledgeDocumentRequestDto:
kwargs: dict[str, Any] = {}
if self.is_disabled is not None:
kwargs["is_disabled"] = self.is_disabled
if self.title is not None:
kwargs["title"] = self.title
if self.scopes is not None:
kwargs["scopes"] = self.scopes
return PatchKnowledgeDocumentRequestDto(_check_type=False, **kwargs)
56 changes: 56 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/compute/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
from gooddata_api_client.model.search_request import SearchRequest
from gooddata_api_client.model.search_result import SearchResult

from gooddata_sdk.catalog.knowledge.model.knowledge_document import (
CatalogListKnowledgeDocumentsResponse,
CatalogPatchKnowledgeDocumentRequest,
)
from gooddata_sdk.client import GoodDataApiClient
from gooddata_sdk.compute.model.execution import (
Execution,
Expand Down Expand Up @@ -350,3 +354,55 @@ def sync_metadata(self, workspace_id: str, async_req: bool = False) -> None:
None
"""
self._actions_api.metadata_sync(workspace_id, async_req=async_req, _check_return_type=False)

def list_knowledge_documents(
self,
workspace_id: str,
*,
size: int | None = None,
page_token: str | None = None,
meta_include: str | None = None,
) -> CatalogListKnowledgeDocumentsResponse:
"""
List knowledge documents in a GoodData workspace with optional cursor-based pagination.

Args:
workspace_id (str): workspace identifier
size (int, optional): number of documents per page. Defaults to server default (50).
page_token (str, optional): opaque cursor for fetching the next page.
meta_include (str, optional): set to 'page' to include totalCount in response.
Returns:
CatalogListKnowledgeDocumentsResponse: list response with documents and optional pagination info
"""
kwargs: dict[str, Any] = {}
if size is not None:
kwargs["size"] = size
if page_token is not None:
kwargs["page_token"] = page_token
if meta_include is not None:
kwargs["meta_include"] = meta_include
response = self._actions_api.list_documents(workspace_id, _check_return_type=False, **kwargs)
return CatalogListKnowledgeDocumentsResponse.from_api(response)

def patch_knowledge_document(
self,
workspace_id: str,
filename: str,
patch_request: CatalogPatchKnowledgeDocumentRequest,
) -> None:
"""
Patch (partially update) a knowledge document in a GoodData workspace.

Args:
workspace_id (str): workspace identifier
filename (str): filename of the knowledge document to patch
patch_request (CatalogPatchKnowledgeDocumentRequest): patch fields (is_disabled, title, scopes)
Returns:
None
"""
self._actions_api.patch_document(
workspace_id,
filename,
patch_request.as_api_model(),
_check_return_type=False,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# (C) 2024 GoodData Corporation
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# (C) 2024 GoodData Corporation
from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from gooddata_sdk.catalog.knowledge.model.knowledge_document import (
CatalogKnowledgeDocumentMetadata,
CatalogListKnowledgeDocumentsResponse,
CatalogPatchKnowledgeDocumentRequest,
)


class TestCatalogKnowledgeDocumentMetadata:
def test_from_api_required_fields(self):
dto = {
"filename": "test.pdf",
"scopes": ["scope1"],
}
result = CatalogKnowledgeDocumentMetadata.from_api(dto)
assert result.filename == "test.pdf"
assert result.scopes == ["scope1"]
assert result.title is None
assert result.is_disabled is None

def test_from_api_with_optional_fields(self):
dto = {
"filename": "test.pdf",
"title": "Test Document",
"is_disabled": True,
"scopes": ["scope1", "scope2"],
}
result = CatalogKnowledgeDocumentMetadata.from_api(dto)
assert result.filename == "test.pdf"
assert result.title == "Test Document"
assert result.is_disabled is True
assert result.scopes == ["scope1", "scope2"]

def test_from_api_is_disabled_false(self):
dto = {
"filename": "test.pdf",
"is_disabled": False,
"scopes": [],
}
result = CatalogKnowledgeDocumentMetadata.from_api(dto)
assert result.is_disabled is False


class TestCatalogPatchKnowledgeDocumentRequest:
def test_as_api_model_empty(self):
req = CatalogPatchKnowledgeDocumentRequest()
model = req.as_api_model()
# only _check_type was passed, no other kwargs
assert not hasattr(model, "is_disabled") or model.get("is_disabled") is None
assert not hasattr(model, "title") or model.get("title") is None

def test_as_api_model_with_is_disabled(self):
req = CatalogPatchKnowledgeDocumentRequest(is_disabled=True)
model = req.as_api_model()
assert model["is_disabled"] is True

def test_as_api_model_with_title(self):
req = CatalogPatchKnowledgeDocumentRequest(title="New Title")
model = req.as_api_model()
assert model["title"] == "New Title"

def test_as_api_model_with_scopes(self):
req = CatalogPatchKnowledgeDocumentRequest(scopes=["workspace1"])
model = req.as_api_model()
assert model["scopes"] == ["workspace1"]

def test_as_api_model_all_fields(self):
req = CatalogPatchKnowledgeDocumentRequest(is_disabled=False, title="Doc", scopes=["s1"])
model = req.as_api_model()
assert model["is_disabled"] is False
assert model["title"] == "Doc"
assert model["scopes"] == ["s1"]


class TestCatalogListKnowledgeDocumentsResponse:
def test_from_api_minimal(self):
doc_dto = {"filename": "doc.pdf", "scopes": []}
dto = {"documents": [doc_dto]}
result = CatalogListKnowledgeDocumentsResponse.from_api(dto)
assert len(result.documents) == 1
assert result.documents[0].filename == "doc.pdf"
assert result.total_count is None
assert result.next_page_token is None

def test_from_api_with_pagination(self):
doc_dto = {"filename": "doc.pdf", "scopes": []}
dto = {"documents": [doc_dto], "total_count": 100, "next_page_token": "cursor123"}
result = CatalogListKnowledgeDocumentsResponse.from_api(dto)
assert result.total_count == 100
assert result.next_page_token == "cursor123"

def test_from_api_empty_documents(self):
dto = {"documents": []}
result = CatalogListKnowledgeDocumentsResponse.from_api(dto)
assert result.documents == []


class TestComputeServiceKnowledgeDocuments:
def _make_service(self):
from gooddata_sdk.compute.service import ComputeService

api_client = MagicMock()
service = ComputeService.__new__(ComputeService)
service._api_client = api_client
service._actions_api = api_client.actions_api
return service

def test_list_knowledge_documents_no_optional_params(self):
service = self._make_service()
mock_response = {"documents": [], "total_count": 0}
service._actions_api.list_documents.return_value = mock_response

result = service.list_knowledge_documents("workspace1")

service._actions_api.list_documents.assert_called_once_with("workspace1", _check_return_type=False)
assert isinstance(result, CatalogListKnowledgeDocumentsResponse)

def test_list_knowledge_documents_with_pagination_params(self):
service = self._make_service()
mock_response = {"documents": [], "total_count": 50, "next_page_token": "tok"}
service._actions_api.list_documents.return_value = mock_response

result = service.list_knowledge_documents("workspace1", size=10, page_token="prev_tok", meta_include="page")

service._actions_api.list_documents.assert_called_once_with(
"workspace1",
_check_return_type=False,
size=10,
page_token="prev_tok",
meta_include="page",
)
assert result.total_count == 50
assert result.next_page_token == "tok"

def test_patch_knowledge_document(self):
service = self._make_service()
service._actions_api.patch_document.return_value = None

patch_req = CatalogPatchKnowledgeDocumentRequest(is_disabled=True)
service.patch_knowledge_document("workspace1", "doc.pdf", patch_req)

service._actions_api.patch_document.assert_called_once()
call_args = service._actions_api.patch_document.call_args
assert call_args[0][0] == "workspace1"
assert call_args[0][1] == "doc.pdf"
assert call_args[1]["_check_return_type"] is False
Loading