Skip to content

Commit a90d91f

Browse files
committed
get_documents call
1 parent c5fa8ba commit a90d91f

File tree

8 files changed

+252
-12
lines changed

8 files changed

+252
-12
lines changed

polarion_rest_api_client/base_client.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from polarion_rest_api_client import data_models as dm
1010

1111
WorkItemType = t.TypeVar("WorkItemType", bound=dm.WorkItem)
12+
DocumentType = t.TypeVar("DocumentType", bound=dm.Document)
1213

1314

1415
class DefaultFields:
@@ -17,6 +18,7 @@ class DefaultFields:
1718
_workitems: str = "@basic"
1819
_linkedworkitems: str = "id,role,suspect"
1920
_workitem_attachments: str = "@basic"
21+
_documents: str = "@basic"
2022

2123
@property
2224
def workitems(self):
@@ -45,11 +47,23 @@ def workitem_attachments(self):
4547
def workitem_attachments(self, value):
4648
self._workitem_attachments = value
4749

50+
@property
51+
def documents(self):
52+
"""Return the fields dict for document."""
53+
return {"documents": self._documents}
54+
55+
@documents.setter
56+
def documents(self, value):
57+
self._documents = value
58+
4859
@property
4960
def all_types(self):
5061
"""Return all fields dicts merged together."""
5162
return (
52-
self.workitem_attachments | self.workitems | self.linkedworkitems
63+
self.workitem_attachments
64+
| self.workitems
65+
| self.linkedworkitems
66+
| self.documents
5367
)
5468

5569

polarion_rest_api_client/client.py

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
patch_work_item,
3636
post_work_items,
3737
)
38+
from polarion_rest_api_client.open_api_client.api.documents import get_document
3839

3940
logger = logging.getLogger(__name__)
4041

@@ -611,16 +612,20 @@ def get_work_items(
611612
self._work_item(
612613
work_item_id,
613614
unset_str_builder(work_item.attributes.title),
614-
unset_str_builder(
615-
work_item.attributes.description.type
616-
)
617-
if work_item.attributes.description
618-
else None,
619-
unset_str_builder(
620-
work_item.attributes.description.value
621-
)
622-
if work_item.attributes.description
623-
else None,
615+
(
616+
unset_str_builder(
617+
work_item.attributes.description.type
618+
)
619+
if work_item.attributes.description
620+
else None
621+
),
622+
(
623+
unset_str_builder(
624+
work_item.attributes.description.value
625+
)
626+
if work_item.attributes.description
627+
else None
628+
),
624629
unset_str_builder(work_item.attributes.type),
625630
unset_str_builder(work_item.attributes.status),
626631
work_item.attributes.additional_properties,
@@ -635,6 +640,66 @@ def get_work_items(
635640

636641
return work_items, next_page
637642

643+
def get_document(
644+
self,
645+
space_id: str,
646+
document_name: str,
647+
fields: dict[str, str] | None = None,
648+
include: str | None = None,
649+
revision: str | None = None,
650+
retry: bool = True,
651+
) -> base_client.DocumentType:
652+
"""Return the document with the given document_name and space_id."""
653+
654+
if " " in space_id or " " in document_name:
655+
space_id = space_id.replace(" ", "%20")
656+
document_name = document_name.replace(" ", "%20")
657+
if fields is None:
658+
fields = self.default_fields.documents
659+
660+
sparse_fields = _build_sparse_fields(fields)
661+
response = get_document.sync_detailed(
662+
self.project_id,
663+
space_id,
664+
document_name,
665+
client=self.client,
666+
fields=sparse_fields,
667+
include=include,
668+
revision=revision,
669+
)
670+
671+
if not self._check_response(response, not retry) and retry:
672+
sleep_random_time()
673+
return self.get_work_items(
674+
space_id, document_name, fields, include, revision, False
675+
)
676+
677+
document_response = response.parsed
678+
679+
if isinstance(
680+
document_response, api_models.DocumentsSingleGetResponse
681+
) and (data := document_response.data):
682+
if not getattr(data.meta, "errors", []):
683+
assert (attributes := data.attributes)
684+
assert isinstance(data.id, str)
685+
686+
document: base_client.DocumentType = dm.Document(
687+
id=data.id,
688+
module_folder=unset_str_builder(attributes.module_folder),
689+
module_name=unset_str_builder(attributes.module_name),
690+
type=unset_str_builder(attributes.type),
691+
status=unset_str_builder(attributes.status),
692+
home_page_content={
693+
"type": attributes.home_page_content.type,
694+
"value": attributes.home_page_content.value,
695+
}
696+
if not isinstance(
697+
attributes.home_page_content, oa_types.Unset
698+
)
699+
else None,
700+
)
701+
return document
702+
638703
def create_work_items(self, work_items: list[base_client.WorkItemType]):
639704
"""Create the given list of work items."""
640705
current_batch = api_models.WorkitemsListPostRequest([])

polarion_rest_api_client/data_models.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,74 @@ class WorkItemAttachment:
151151
content_bytes: bytes | None = None
152152
mime_type: str | None = None
153153
file_name: str | None = None
154+
155+
156+
class Document:
157+
"""A data class containing all relevant data of a Polarion WorkItem."""
158+
159+
id: str | None = None
160+
module_folder: str | None = None
161+
module_name: str | None = None
162+
type: str | None = None
163+
status: str | None = None
164+
home_page_content: dict | None = None
165+
_checksum: str | None = None
166+
167+
def __init__(
168+
self,
169+
id: str | None = None,
170+
module_folder: str | None = None,
171+
module_name: str | None = None,
172+
type: str | None = None,
173+
status: str | None = None,
174+
home_page_content: dict | None = None,
175+
):
176+
self.id = id
177+
self.module_folder = module_folder
178+
self.module_name = module_name
179+
self.type = type
180+
self.status = status
181+
self.home_page_content = home_page_content
182+
self._checksum = None
183+
184+
def __eq__(self, other: object) -> bool:
185+
"""Compare only Document attributes."""
186+
if not isinstance(other, Document):
187+
return NotImplemented
188+
if self.get_current_checksum() is None:
189+
self.calculate_checksum()
190+
if other.get_current_checksum() is None:
191+
other.calculate_checksum()
192+
193+
return self.get_current_checksum() == other.get_current_checksum()
194+
195+
def to_dict(self) -> dict[str, t.Any]:
196+
"""Return the content of the Document as dictionary."""
197+
return {
198+
"id": self.id,
199+
"module_folder": self.module_folder,
200+
"module_name": self.module_name,
201+
"type": self.type,
202+
"status": self.status,
203+
"home_page_content": self.home_page_content,
204+
"checksum": self._checksum,
205+
}
206+
207+
def calculate_checksum(self) -> str:
208+
"""Calculate and return a checksum for this Document.
209+
210+
In addition, the checksum will be written to self._checksum.
211+
"""
212+
data = self.to_dict()
213+
del data["checksum"]
214+
del data["id"]
215+
216+
data = dict(sorted(data.items()))
217+
218+
converted = json.dumps(data).encode("utf8")
219+
self._checksum = hashlib.sha256(converted).hexdigest()
220+
return self._checksum
221+
222+
def get_current_checksum(self) -> str | None:
223+
"""Return the checksum currently set without calculation."""
224+
return self._checksum

tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
TEST_RESPONSES / "workitems_next_page_error.json"
6060
)
6161
TEST_WI_NEXT_PAGE_RESPONSE = TEST_RESPONSES / "workitems_next_page.json"
62-
62+
TEST_DOCUMENT_RESPONSE = TEST_RESPONSES / "get_document.json"
6363
TEST_ERROR_RESPONSE = TEST_RESPONSES / "error.json"
6464
TEST_PROJECT_RESPONSE_JSON = TEST_RESPONSES / "project.json"
6565

tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,15 @@ def fixture_dummy_work_item_attachment():
6969
"text/plain",
7070
"test.json",
7171
)
72+
73+
74+
@pytest.fixture(name="document")
75+
def fixture_dummy_document():
76+
return polarion_api.Document(
77+
"MyProjectId/MySpaceId/MyDocumentName",
78+
"MySpaceId",
79+
"MyDocumentName",
80+
"standardSpecification",
81+
"open",
82+
{"type": "text/html", "value": "<h1>My text value</h1>"},
83+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"data": {
3+
"attributes": {
4+
"autoSuspect": {},
5+
"branchedWithQuery": {},
6+
"derivedFromLinkRole": {},
7+
"created": "1970-01-01T00:00:00Z",
8+
"homePageContent": {
9+
"type": "text/html",
10+
"value": "<h1>My text value</h1>"
11+
},
12+
"moduleFolder": "MySpaceId",
13+
"moduleName": "MyDocumentName",
14+
"outlineNumbering": {},
15+
"renderingLayouts": [],
16+
"status": "open",
17+
"structureLinkRole": "parent",
18+
"title": "MyDocumentName",
19+
"type": "standardSpecification",
20+
"updated": "1970-01-01T00:00:00Z",
21+
"usesOutlineNumbering": true
22+
},
23+
"id": "MyProjectId/MySpaceId/MyDocumentName",
24+
"links": {
25+
"self": "server-host-name/application-path/projects/MyProjectId/spaces/MySpaceId/documents/MyDocumentName"
26+
},
27+
"meta": {},
28+
"relationships": {},
29+
"revision": {},
30+
"type": "documents"
31+
},
32+
"included": [],
33+
"links": {
34+
"self": "server-host-name/application-path/projects/MyProjectId/spaces/MySpaceId/documents/MyDocumentName"
35+
}
36+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Copyright DB Netz AG and contributors
2+
SPDX-License-Identifier: Apache-2.0

tests/test_client_documents.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright DB Netz AG and contributors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
import copy
7+
import json
8+
9+
import pytest
10+
import pytest_httpx
11+
import pytest_mock as mock
12+
13+
import polarion_rest_api_client as polarion_api
14+
from polarion_rest_api_client.open_api_client import models as api_models
15+
from tests import TEST_DOCUMENT_RESPONSE
16+
17+
18+
def test_get_document_with_all_fields(
19+
client: polarion_api.OpenAPIPolarionProjectClient,
20+
httpx_mock: pytest_httpx.HTTPXMock,
21+
):
22+
with open(TEST_DOCUMENT_RESPONSE, encoding="utf8") as f:
23+
httpx_mock.add_response(json=json.load(f))
24+
25+
client.default_fields.documents = "@all"
26+
27+
document = client.get_document("MySpaceId", "MyDocumentName")
28+
29+
reqs = httpx_mock.get_requests()
30+
assert reqs[0].method == "GET"
31+
assert isinstance(document, polarion_api.data_models.Document)
32+
assert len(reqs) == 1
33+
assert document == polarion_api.data_models.Document(
34+
"MyProjectId/MySpaceId/MyDocumentName",
35+
"MySpaceId",
36+
"MyDocumentName",
37+
"standardSpecification",
38+
"open",
39+
{"type": "text/html", "value": "<h1>My text value</h1>"},
40+
)

0 commit comments

Comments
 (0)