Skip to content

Commit 1aa10bd

Browse files
authored
feat: Add documents endpoint
Merge pull request #16 from DSD-DBS/add-documents
2 parents 440684b + 7603762 commit 1aa10bd

File tree

8 files changed

+279
-25
lines changed

8 files changed

+279
-25
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ MANIFEST
3939
pip-log.txt
4040
pip-delete-this-directory.txt
4141

42+
# IDE
43+
.vscode
44+
4245
# Unit test / coverage reports
4346
htmlcov/
4447
.tox/

polarion_rest_api_client/base_client.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class DefaultFields:
1717
_workitems: str = "@basic"
1818
_linkedworkitems: str = "id,role,suspect"
1919
_workitem_attachments: str = "@basic"
20+
_documents: str = "@basic"
2021

2122
@property
2223
def workitems(self):
@@ -45,11 +46,23 @@ def workitem_attachments(self):
4546
def workitem_attachments(self, value):
4647
self._workitem_attachments = value
4748

49+
@property
50+
def documents(self):
51+
"""Return the fields dict for document."""
52+
return {"documents": self._documents}
53+
54+
@documents.setter
55+
def documents(self, value):
56+
self._documents = value
57+
4858
@property
4959
def all_types(self):
5060
"""Return all fields dicts merged together."""
5161
return (
52-
self.workitem_attachments | self.workitems | self.linkedworkitems
62+
self.workitem_attachments
63+
| self.workitems
64+
| self.linkedworkitems
65+
| self.documents
5366
)
5467

5568

polarion_rest_api_client/client.py

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
import random
1111
import time
1212
import typing as t
13+
import urllib.parse
1314

1415
from polarion_rest_api_client import base_client
1516
from polarion_rest_api_client import data_models as dm
1617
from polarion_rest_api_client import errors
1718
from polarion_rest_api_client.open_api_client import client as oa_client
1819
from polarion_rest_api_client.open_api_client import models as api_models
1920
from polarion_rest_api_client.open_api_client import types as oa_types
21+
from polarion_rest_api_client.open_api_client.api.documents import get_document
2022
from polarion_rest_api_client.open_api_client.api.linked_work_items import (
2123
delete_linked_work_items,
2224
get_linked_work_items,
@@ -611,16 +613,20 @@ def get_work_items(
611613
self._work_item(
612614
work_item_id,
613615
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,
616+
(
617+
unset_str_builder(
618+
work_item.attributes.description.type
619+
)
620+
if work_item.attributes.description
621+
else None
622+
),
623+
(
624+
unset_str_builder(
625+
work_item.attributes.description.value
626+
)
627+
if work_item.attributes.description
628+
else None
629+
),
624630
unset_str_builder(work_item.attributes.type),
625631
unset_str_builder(work_item.attributes.status),
626632
work_item.attributes.additional_properties,
@@ -635,6 +641,89 @@ def get_work_items(
635641

636642
return work_items, next_page
637643

644+
def get_document(
645+
self,
646+
space_id: str,
647+
document_name: str,
648+
fields: dict[str, str] | None = None,
649+
include: str | None = None,
650+
revision: str | None = None,
651+
retry: bool = True,
652+
) -> dm.Document:
653+
"""Return the document with the given document_name and space_id."""
654+
655+
if " " in space_id or " " in document_name:
656+
space_id = urllib.parse.quote(
657+
space_id, safe="/", encoding=None, errors=None
658+
)
659+
document_name = urllib.parse.quote(
660+
document_name, safe="/", encoding=None, errors=None
661+
)
662+
if fields is None:
663+
fields = self.default_fields.documents
664+
665+
sparse_fields = _build_sparse_fields(fields)
666+
response = get_document.sync_detailed(
667+
self.project_id,
668+
space_id,
669+
document_name,
670+
client=self.client,
671+
fields=sparse_fields,
672+
include=include,
673+
revision=revision,
674+
)
675+
676+
if not self._check_response(response, not retry) and retry:
677+
sleep_random_time()
678+
return self.get_document(
679+
space_id, document_name, fields, include, revision, False
680+
)
681+
682+
document_response = response.parsed
683+
684+
if isinstance(
685+
document_response, api_models.DocumentsSingleGetResponse
686+
) and (data := document_response.data):
687+
if not getattr(data.meta, "errors", []):
688+
assert (attributes := data.attributes)
689+
assert isinstance(data.id, str)
690+
home_page_content = self._handle_home_page_content(
691+
attributes.home_page_content
692+
)
693+
694+
document: dm.Document = dm.Document(
695+
id=data.id,
696+
module_folder=unset_str_builder(attributes.module_folder),
697+
module_name=unset_str_builder(attributes.module_name),
698+
type=unset_str_builder(attributes.type),
699+
status=unset_str_builder(attributes.status),
700+
home_page_content=home_page_content,
701+
)
702+
return document
703+
704+
def _handle_home_page_content(
705+
self,
706+
home_page_content: api_models.DocumentsSingleGetResponseDataAttributesHomePageContent
707+
| oa_types.Unset,
708+
) -> dm.TextContent | None:
709+
if isinstance(home_page_content, oa_types.Unset):
710+
return None
711+
712+
home_page_content_type = None
713+
home_page_content_value = None
714+
715+
if isinstance(
716+
home_page_content.type,
717+
api_models.DocumentsSingleGetResponseDataAttributesHomePageContentType,
718+
):
719+
home_page_content_type = str(home_page_content.type)
720+
if isinstance(home_page_content.value, str):
721+
home_page_content_value = home_page_content.value
722+
return dm.TextContent(
723+
type=home_page_content_type,
724+
value=home_page_content_value,
725+
)
726+
638727
def create_work_items(self, work_items: list[base_client.WorkItemType]):
639728
"""Create the given list of work items."""
640729
current_batch = api_models.WorkitemsListPostRequest([])

polarion_rest_api_client/data_models.py

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,68 @@
1010
import typing as t
1111

1212

13-
class WorkItem:
14-
"""A data class containing all relevant data of a Polarion WorkItem."""
13+
@dataclasses.dataclass
14+
class BaseItem:
15+
"""A parent data class for WorkItem and Document."""
1516

1617
id: str | None = None
18+
type: str | None = None
19+
status: str | None = None
20+
_checksum: str | None = dataclasses.field(init=False, default=None)
21+
22+
def __post_init__(self):
23+
"""Set initial checksum value."""
24+
self._checksum = self.calculate_checksum()
25+
26+
def __eq__(self, other: object) -> bool:
27+
"""Compare only BaseItem attributes."""
28+
if not isinstance(other, BaseItem):
29+
return NotImplemented
30+
if self.get_current_checksum() is None:
31+
self.calculate_checksum()
32+
if other.get_current_checksum() is None:
33+
other.calculate_checksum()
34+
35+
return self.get_current_checksum() == other.get_current_checksum()
36+
37+
def to_dict(self) -> dict[str, t.Any]:
38+
"""Return the content of the BaseItem as dictionary."""
39+
return {
40+
"id": self.id,
41+
"type": self.type,
42+
"status": self.status,
43+
"checksum": self._checksum,
44+
}
45+
46+
def calculate_checksum(self) -> str:
47+
"""Calculate and return a checksum for this BaseItem.
48+
49+
In addition, the checksum will be written to self._checksum.
50+
"""
51+
data = self.to_dict()
52+
del data["checksum"]
53+
del data["id"]
54+
55+
data = dict(sorted(data.items()))
56+
57+
converted = json.dumps(data).encode("utf8")
58+
self._checksum = hashlib.sha256(converted).hexdigest()
59+
return self._checksum
60+
61+
def get_current_checksum(self) -> str | None:
62+
"""Return the checksum currently set without calculation."""
63+
return self._checksum
64+
65+
66+
class WorkItem(BaseItem):
67+
"""A data class containing all relevant data of a Polarion WorkItem."""
68+
1769
title: str | None = None
1870
description_type: str | None = None
1971
description: str | None = None
20-
type: str | None = None
21-
status: str | None = None
2272
additional_attributes: dict[str, t.Any] = {}
2373
linked_work_items: list[WorkItemLink] = []
2474
attachments: list[WorkItemAttachment] = []
25-
_checksum: str | None = None
2675

2776
def __init__(
2877
self,
@@ -37,14 +86,11 @@ def __init__(
3786
attachments: list[WorkItemAttachment] | None = None,
3887
**kwargs,
3988
):
40-
self.id = id
89+
super().__init__(id, type, status)
4190
self.title = title
4291
self.description_type = description_type
4392
self.description = description
44-
self.type = type
45-
self.status = status
4693
self.additional_attributes = (additional_attributes or {}) | kwargs
47-
self._checksum = self.additional_attributes.pop("checksum", None)
4894
self.linked_work_items = linked_work_items or []
4995
self.attachments = attachments or []
5096

@@ -125,10 +171,6 @@ def calculate_checksum(self) -> str:
125171
self._checksum = hashlib.sha256(converted).hexdigest()
126172
return self._checksum
127173

128-
def get_current_checksum(self) -> str | None:
129-
"""Return the checksum currently set without calculation."""
130-
return self._checksum
131-
132174

133175
@dataclasses.dataclass
134176
class WorkItemLink:
@@ -157,3 +199,33 @@ class WorkItemAttachment:
157199
content_bytes: bytes | None = None
158200
mime_type: str | None = None
159201
file_name: str | None = None
202+
203+
204+
class Document(BaseItem):
205+
"""A data class containing all relevant data of a Polarion Document."""
206+
207+
module_folder: str | None = None
208+
module_name: str | None = None
209+
home_page_content: TextContent | None = None
210+
211+
def __init__(
212+
self,
213+
id: str | None = None,
214+
module_folder: str | None = None,
215+
module_name: str | None = None,
216+
type: str | None = None,
217+
status: str | None = None,
218+
home_page_content: TextContent | None = None,
219+
):
220+
super().__init__(id, type, status)
221+
self.module_folder = module_folder
222+
self.module_name = module_name
223+
self.home_page_content = home_page_content
224+
225+
226+
@dataclasses.dataclass
227+
class TextContent:
228+
"""A data class for home_page_content of a Polarion Document."""
229+
230+
type: str | None = None
231+
value: str | None = None

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

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

0 commit comments

Comments
 (0)