unique_toolkit 0.7.9__py3-none-any.whl → 1.33.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unique_toolkit/__init__.py +36 -3
- unique_toolkit/_common/api_calling/human_verification_manager.py +357 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +225 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +368 -0
- unique_toolkit/_common/endpoint_requestor.py +480 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
- unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +174 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/image/encode.py +25 -0
- unique_toolkit/_common/utils/jinja/helpers.py +10 -0
- unique_toolkit/_common/utils/jinja/render.py +18 -0
- unique_toolkit/_common/utils/jinja/schema.py +65 -0
- unique_toolkit/_common/utils/jinja/utils.py +80 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
- unique_toolkit/agentic/history_manager/history_manager.py +241 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
- unique_toolkit/agentic/message_log_manager/service.py +93 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +128 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
- unique_toolkit/agentic/tools/schemas.py +145 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +187 -0
- unique_toolkit/agentic/tools/tool_manager.py +492 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +9 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/fast_api_factory.py +131 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +206 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/app/webhook.py +77 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +648 -78
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +134 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +353 -8
- unique_toolkit/content/schemas.py +128 -15
- unique_toolkit/content/service.py +321 -45
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +10 -3
- unique_toolkit/data_extraction/README.md +96 -0
- unique_toolkit/data_extraction/__init__.py +11 -0
- unique_toolkit/data_extraction/augmented/__init__.py +5 -0
- unique_toolkit/data_extraction/augmented/service.py +93 -0
- unique_toolkit/data_extraction/base.py +25 -0
- unique_toolkit/data_extraction/basic/__init__.py +11 -0
- unique_toolkit/data_extraction/basic/config.py +18 -0
- unique_toolkit/data_extraction/basic/prompt.py +13 -0
- unique_toolkit/data_extraction/basic/service.py +55 -0
- unique_toolkit/embedding/service.py +103 -12
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +84 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/_responses_api_utils.py +93 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +345 -43
- unique_toolkit/language_model/infos.py +1288 -46
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +481 -49
- unique_toolkit/language_model/service.py +229 -28
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1631 -0
- unique_toolkit/services/knowledge_base.py +1094 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
- unique_toolkit-1.33.3.dist-info/RECORD +205 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.9.dist-info/METADATA +0 -413
- unique_toolkit-0.7.9.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from docx.document import Document as DocumentObject
|
|
2
|
+
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
3
|
+
from docxtpl import DocxTemplate
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HeadingField(BaseModel):
|
|
8
|
+
text: str
|
|
9
|
+
level: int = 4
|
|
10
|
+
alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT
|
|
11
|
+
|
|
12
|
+
def add(self, doc: DocumentObject):
|
|
13
|
+
p = doc.add_heading(self.text, level=self.level)
|
|
14
|
+
p.alignment = self.alignment
|
|
15
|
+
return p
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"HeadingField(text={self.text}, level={self.level}, alignment={self.alignment})"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ParagraphField(BaseModel):
|
|
22
|
+
text: str
|
|
23
|
+
style: str | None = None
|
|
24
|
+
alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT
|
|
25
|
+
|
|
26
|
+
def add(self, doc: DocumentObject):
|
|
27
|
+
p = doc.add_paragraph(self.text, style=self.style)
|
|
28
|
+
p.alignment = self.alignment
|
|
29
|
+
return p
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f"ParagraphField(text={self.text}, style={self.style}, alignment={self.alignment})"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RunField(BaseModel):
|
|
36
|
+
text: str
|
|
37
|
+
italic: bool | None = False
|
|
38
|
+
bold: bool | None = False
|
|
39
|
+
alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT
|
|
40
|
+
|
|
41
|
+
def __str__(self):
|
|
42
|
+
return f"RunField(text={self.text}, italic={self.italic}, alignment={self.alignment})"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RunsField(BaseModel):
|
|
46
|
+
runs: list[RunField]
|
|
47
|
+
style: str | None = None
|
|
48
|
+
alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT
|
|
49
|
+
|
|
50
|
+
def add(self, doc: DocumentObject):
|
|
51
|
+
if not self.runs:
|
|
52
|
+
return None
|
|
53
|
+
p = doc.add_paragraph(style=self.style)
|
|
54
|
+
for run in self.runs:
|
|
55
|
+
r = p.add_run(run.text)
|
|
56
|
+
if run.italic:
|
|
57
|
+
r.italic = True
|
|
58
|
+
if run.bold:
|
|
59
|
+
r.bold = True
|
|
60
|
+
return p
|
|
61
|
+
|
|
62
|
+
def __str__(self):
|
|
63
|
+
return f"RunsField(runs={self.runs}, style={self.style}, alignment={self.alignment})"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ContentField(BaseModel):
|
|
67
|
+
contents: list[HeadingField | ParagraphField | RunsField]
|
|
68
|
+
|
|
69
|
+
def add(self, doc: DocxTemplate):
|
|
70
|
+
sd = doc.new_subdoc()
|
|
71
|
+
for content in self.contents:
|
|
72
|
+
# if isinstance(content, ImageField):
|
|
73
|
+
# content.download_image(self.download_path)
|
|
74
|
+
# content.add(sd) # type: ignore
|
|
75
|
+
# else:
|
|
76
|
+
content.add(sd) # type: ignore
|
|
77
|
+
return sd
|
|
78
|
+
|
|
79
|
+
def __str__(self):
|
|
80
|
+
return f"ContentField(contents={self.contents})"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from docxtpl import DocxTemplate
|
|
7
|
+
from markdown_it import MarkdownIt
|
|
8
|
+
|
|
9
|
+
from unique_toolkit._common.docx_generator.config import DocxGeneratorConfig
|
|
10
|
+
from unique_toolkit._common.docx_generator.schemas import (
|
|
11
|
+
ContentField,
|
|
12
|
+
HeadingField,
|
|
13
|
+
ParagraphField,
|
|
14
|
+
RunField,
|
|
15
|
+
RunsField,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
generator_dir_path = Path(__file__).resolve().parent
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DocxGeneratorService:
|
|
25
|
+
def __init__(self, config: DocxGeneratorConfig, *, template: bytes | None = None):
|
|
26
|
+
self._config = config
|
|
27
|
+
self._template = template
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def parse_markdown_to_list_content_fields(
|
|
31
|
+
markdown: str, offset_header_lvl: int = 0
|
|
32
|
+
) -> list[HeadingField | ParagraphField | RunsField]:
|
|
33
|
+
# Initialize markdown-it parser
|
|
34
|
+
md = MarkdownIt()
|
|
35
|
+
|
|
36
|
+
# Preprocess markdown.
|
|
37
|
+
# - Replace all headings with the correct heading level
|
|
38
|
+
# - Remove "Relevant sources" heading
|
|
39
|
+
# - Replace "# Proposed answer" with "#### Proposed answer"
|
|
40
|
+
markdown = re.sub(r"(?m)^\s*## ", "#### ", markdown)
|
|
41
|
+
markdown = re.sub(r"(?m)^\s*### ", "##### ", markdown)
|
|
42
|
+
markdown = markdown.replace("# Relevant sources", "")
|
|
43
|
+
markdown = markdown.replace("# Proposed answer", "#### Proposed answer")
|
|
44
|
+
|
|
45
|
+
tokens = md.parse(markdown)
|
|
46
|
+
|
|
47
|
+
elements = []
|
|
48
|
+
current_section = {}
|
|
49
|
+
in_list = False
|
|
50
|
+
bullet_list_indent_level = 0
|
|
51
|
+
list_item_open = False
|
|
52
|
+
|
|
53
|
+
for token in tokens:
|
|
54
|
+
if token.type == "bullet_list_open":
|
|
55
|
+
in_list = True
|
|
56
|
+
bullet_list_indent_level = int(token.level / 2)
|
|
57
|
+
|
|
58
|
+
elif token.type == "bullet_list_close":
|
|
59
|
+
in_list = False
|
|
60
|
+
bullet_list_indent_level = 0
|
|
61
|
+
|
|
62
|
+
elif token.type == "list_item_open":
|
|
63
|
+
if list_item_open:
|
|
64
|
+
elements.append(current_section)
|
|
65
|
+
list_item_open = True
|
|
66
|
+
list_level = token.level - bullet_list_indent_level
|
|
67
|
+
current_section = {
|
|
68
|
+
"type": RunsField,
|
|
69
|
+
"runs": [],
|
|
70
|
+
"is_list_item": True,
|
|
71
|
+
"level": list_level,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
elif token.type == "list_item_close":
|
|
75
|
+
if current_section and current_section.get("runs"):
|
|
76
|
+
elements.append(current_section)
|
|
77
|
+
current_section = {}
|
|
78
|
+
list_item_open = False
|
|
79
|
+
|
|
80
|
+
if token.type == "heading_open":
|
|
81
|
+
# Heading start, token.tag gives the level (e.g., 'h1', 'h2', etc.)
|
|
82
|
+
header_lvl = int(token.tag[1]) # Extract the level number from tag
|
|
83
|
+
current_section = {
|
|
84
|
+
"type": HeadingField,
|
|
85
|
+
"text": "",
|
|
86
|
+
"level": header_lvl + offset_header_lvl,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
elif token.type == "heading_close":
|
|
90
|
+
if current_section:
|
|
91
|
+
elements.append(current_section)
|
|
92
|
+
current_section = {}
|
|
93
|
+
|
|
94
|
+
elif token.type == "paragraph_open":
|
|
95
|
+
if not in_list: # Only create new paragraph if not in a list
|
|
96
|
+
current_section = {"type": RunsField, "runs": []}
|
|
97
|
+
|
|
98
|
+
elif token.type == "paragraph_close":
|
|
99
|
+
if not in_list and current_section: # Only append if not in a list
|
|
100
|
+
elements.append(current_section)
|
|
101
|
+
current_section = {}
|
|
102
|
+
|
|
103
|
+
elif token.type == "inline":
|
|
104
|
+
if current_section.get("type") == HeadingField:
|
|
105
|
+
content = token.content
|
|
106
|
+
if content.startswith("_page"):
|
|
107
|
+
# replace "_pageXXXX_" with "PageXXXX", where XXXX can be any characters and numbers
|
|
108
|
+
content = re.sub(
|
|
109
|
+
r"^_page([a-zA-Z0-9\s-]+)_(.*?)",
|
|
110
|
+
r"Page\1",
|
|
111
|
+
content,
|
|
112
|
+
)
|
|
113
|
+
bold = True
|
|
114
|
+
current_section["text"] += content
|
|
115
|
+
elif "runs" in current_section:
|
|
116
|
+
bold = False
|
|
117
|
+
italic = False
|
|
118
|
+
runs = []
|
|
119
|
+
if token.children:
|
|
120
|
+
for child in token.children:
|
|
121
|
+
content = child.content
|
|
122
|
+
if child.type == "strong_open":
|
|
123
|
+
bold = True
|
|
124
|
+
elif child.type == "strong_close":
|
|
125
|
+
bold = False
|
|
126
|
+
elif child.type == "em_open":
|
|
127
|
+
italic = True
|
|
128
|
+
elif child.type == "em_close":
|
|
129
|
+
italic = False
|
|
130
|
+
if child.type == "softbreak":
|
|
131
|
+
content += "\n"
|
|
132
|
+
if content: # Only add non-empty content
|
|
133
|
+
runs.append(
|
|
134
|
+
RunField(
|
|
135
|
+
text=content,
|
|
136
|
+
bold=bold,
|
|
137
|
+
italic=italic,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
runs.append(
|
|
142
|
+
RunField(
|
|
143
|
+
text=token.content,
|
|
144
|
+
bold=bold,
|
|
145
|
+
italic=italic,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
current_section["runs"].extend(runs)
|
|
150
|
+
|
|
151
|
+
# Process remaining elements
|
|
152
|
+
contents = []
|
|
153
|
+
for element in elements:
|
|
154
|
+
if not element:
|
|
155
|
+
continue
|
|
156
|
+
if element["type"] == HeadingField:
|
|
157
|
+
contents.append(
|
|
158
|
+
HeadingField(
|
|
159
|
+
text=element["text"],
|
|
160
|
+
level=element["level"],
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
elif element["type"] == RunsField:
|
|
164
|
+
if element.get("is_list_item", False):
|
|
165
|
+
level: int = min(element.get("level", 1), 5)
|
|
166
|
+
if level > 1:
|
|
167
|
+
style = "List Bullet " + str(level)
|
|
168
|
+
else:
|
|
169
|
+
style = "List Bullet"
|
|
170
|
+
contents.append(RunsField(style=style, runs=element["runs"]))
|
|
171
|
+
else:
|
|
172
|
+
contents.append(RunsField(runs=element["runs"]))
|
|
173
|
+
|
|
174
|
+
return contents
|
|
175
|
+
|
|
176
|
+
def generate_from_template(
|
|
177
|
+
self,
|
|
178
|
+
subdoc_content: list[HeadingField | ParagraphField | RunsField],
|
|
179
|
+
fields: dict | None = None,
|
|
180
|
+
):
|
|
181
|
+
"""
|
|
182
|
+
Generate a docx file from a template with the given content.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
subdoc_content (list[HeadingField | ParagraphField | RunsField]): The content to be added to the docx file.
|
|
186
|
+
fields (dict): Other fields to be added to the docx file. Defaults to None.
|
|
187
|
+
"""
|
|
188
|
+
doc = DocxTemplate(io.BytesIO(self.template))
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
content = {}
|
|
192
|
+
content["body"] = ContentField(contents=subdoc_content)
|
|
193
|
+
|
|
194
|
+
if fields:
|
|
195
|
+
content.update(fields)
|
|
196
|
+
|
|
197
|
+
for key, value in content.items():
|
|
198
|
+
if isinstance(value, ContentField):
|
|
199
|
+
content[key] = value.add(doc)
|
|
200
|
+
|
|
201
|
+
doc.render(content)
|
|
202
|
+
docx_rendered_object = io.BytesIO()
|
|
203
|
+
|
|
204
|
+
doc.save(docx_rendered_object)
|
|
205
|
+
docx_rendered_object.seek(0)
|
|
206
|
+
|
|
207
|
+
return docx_rendered_object.getvalue()
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
_LOGGER.error(f"Error generating docx: {e}")
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def _get_default_template(self) -> bytes:
|
|
214
|
+
generator_dir_path = Path(__file__).resolve().parent
|
|
215
|
+
path = generator_dir_path / "template" / "Doc Template.docx"
|
|
216
|
+
|
|
217
|
+
file_content = path.read_bytes()
|
|
218
|
+
|
|
219
|
+
_LOGGER.info("Template downloaded from default template")
|
|
220
|
+
|
|
221
|
+
return file_content
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def template(self) -> bytes:
|
|
225
|
+
return self._template or self._get_default_template()
|
|
Binary file
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a minimal framework for building endpoint classes such that a client can use
|
|
3
|
+
the endpoints without having to know the details of the endpoints.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import warnings
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
from string import Template
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
Generic,
|
|
13
|
+
ParamSpec,
|
|
14
|
+
Protocol,
|
|
15
|
+
TypeVar,
|
|
16
|
+
cast,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ValidationError
|
|
20
|
+
|
|
21
|
+
# Paramspecs
|
|
22
|
+
PayloadParamSpec = ParamSpec("PayloadParamSpec")
|
|
23
|
+
PathParamsSpec = ParamSpec("PathParamsSpec")
|
|
24
|
+
QueryParamsSpec = ParamSpec("QueryParamsSpec")
|
|
25
|
+
|
|
26
|
+
# Type variables
|
|
27
|
+
ResponseType = TypeVar("ResponseType", bound=BaseModel, covariant=True)
|
|
28
|
+
PathParamsType = TypeVar("PathParamsType", bound=BaseModel)
|
|
29
|
+
PayloadType = TypeVar("PayloadType", bound=BaseModel)
|
|
30
|
+
QueryParamsType = TypeVar("QueryParamsType", bound=BaseModel)
|
|
31
|
+
|
|
32
|
+
# Helper type to extract constructor parameters
|
|
33
|
+
|
|
34
|
+
# Type for the constructor of a Pydantic model
|
|
35
|
+
ModelConstructor = Callable[..., BaseModel]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class HttpMethods(StrEnum):
|
|
39
|
+
GET = "GET"
|
|
40
|
+
POST = "POST"
|
|
41
|
+
PUT = "PUT"
|
|
42
|
+
DELETE = "DELETE"
|
|
43
|
+
PATCH = "PATCH"
|
|
44
|
+
OPTIONS = "OPTIONS"
|
|
45
|
+
HEAD = "HEAD"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Backward compatibility TODO: Remove in 2.0.0.
|
|
49
|
+
EndpointMethods = HttpMethods
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ResponseValidationException(Exception):
|
|
53
|
+
"""
|
|
54
|
+
This exception is raised when the response validation fails.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self, operation_name: str, response: dict[str, Any], pydantic_error: str
|
|
59
|
+
):
|
|
60
|
+
super().__init__(
|
|
61
|
+
f"Response validation failed for operation {operation_name}\n"
|
|
62
|
+
f"Response: {response}\n"
|
|
63
|
+
f"Pydantic error: {pydantic_error}"
|
|
64
|
+
)
|
|
65
|
+
self._operation_name = operation_name
|
|
66
|
+
self._response = response
|
|
67
|
+
self._pydantic_error = pydantic_error
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def operation_name(self) -> str:
|
|
71
|
+
return self._operation_name
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def response(self) -> dict[str, Any]:
|
|
75
|
+
return self._response
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def pydantic_error(self) -> str:
|
|
79
|
+
return self._pydantic_error
|
|
80
|
+
|
|
81
|
+
def __str__(self):
|
|
82
|
+
return (
|
|
83
|
+
f"Response validation failed for {self._operation_name}\n"
|
|
84
|
+
+ f"Response: {self._response}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ApiOperationProtocol(
|
|
89
|
+
Protocol,
|
|
90
|
+
Generic[
|
|
91
|
+
PathParamsSpec,
|
|
92
|
+
PathParamsType,
|
|
93
|
+
PayloadParamSpec,
|
|
94
|
+
PayloadType,
|
|
95
|
+
QueryParamsSpec,
|
|
96
|
+
QueryParamsType,
|
|
97
|
+
ResponseType,
|
|
98
|
+
],
|
|
99
|
+
):
|
|
100
|
+
@staticmethod
|
|
101
|
+
def path_dump_options() -> dict[str, Any]: ...
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def path_params_model() -> type[PathParamsType]: ...
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def payload_dump_options() -> dict[str, Any]: ...
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def payload_model() -> type[PayloadType]: ...
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def response_validate_options() -> dict[str, Any]: ...
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def response_model() -> type[ResponseType]: ...
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def query_params_dump_options() -> dict[str, Any]: ...
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def query_params_model() -> type[QueryParamsType]: ...
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def create_path(
|
|
126
|
+
*args: PathParamsSpec.args, **kwargs: PathParamsSpec.kwargs
|
|
127
|
+
) -> str: ...
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def create_path_from_model(
|
|
131
|
+
path_params: PathParamsType, *, model_dump_options: dict | None = None
|
|
132
|
+
) -> str: ...
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def create_payload(
|
|
136
|
+
*args: PayloadParamSpec.args, **kwargs: PayloadParamSpec.kwargs
|
|
137
|
+
) -> dict[str, Any]: ...
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def create_payload_from_model(
|
|
141
|
+
payload: PayloadType, *, model_dump_options: dict | None = None
|
|
142
|
+
) -> dict[str, Any]: ...
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def create_query_params(
|
|
146
|
+
*args: QueryParamsSpec.args, **kwargs: QueryParamsSpec.kwargs
|
|
147
|
+
) -> dict[str, Any]: ...
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def create_query_params_from_model(
|
|
151
|
+
query_params: QueryParamsType, *, model_dump_options: dict | None = None
|
|
152
|
+
) -> dict[str, Any]: ...
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def handle_response(
|
|
156
|
+
response: dict[str, Any], *, model_validate_options: dict | None = None
|
|
157
|
+
) -> ResponseType: ...
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def request_method() -> EndpointMethods: ...
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def models_from_combined(
|
|
164
|
+
combined: dict[str, Any],
|
|
165
|
+
) -> tuple[PathParamsType, PayloadType, QueryParamsType]: ...
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class EmptyModel(BaseModel): ...
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Model for any client to implement
|
|
172
|
+
def build_api_operation(
|
|
173
|
+
*,
|
|
174
|
+
method: HttpMethods,
|
|
175
|
+
path_template: Template,
|
|
176
|
+
response_model_type: type[ResponseType],
|
|
177
|
+
path_params_constructor: Callable[PathParamsSpec, PathParamsType] = EmptyModel,
|
|
178
|
+
payload_dump_options: dict | None = None,
|
|
179
|
+
payload_constructor: Callable[PayloadParamSpec, PayloadType] = EmptyModel,
|
|
180
|
+
path_dump_options: dict | None = None,
|
|
181
|
+
query_params_constructor: Callable[QueryParamsSpec, QueryParamsType] = EmptyModel,
|
|
182
|
+
query_params_dump_options: dict | None = None,
|
|
183
|
+
response_validate_options: dict | None = None,
|
|
184
|
+
dump_options: dict | None = None, # Deprecated
|
|
185
|
+
) -> type[
|
|
186
|
+
ApiOperationProtocol[
|
|
187
|
+
PathParamsSpec,
|
|
188
|
+
PathParamsType,
|
|
189
|
+
PayloadParamSpec,
|
|
190
|
+
PayloadType,
|
|
191
|
+
QueryParamsSpec,
|
|
192
|
+
QueryParamsType,
|
|
193
|
+
ResponseType,
|
|
194
|
+
]
|
|
195
|
+
]:
|
|
196
|
+
"""Generate a class with static methods for endpoint handling.
|
|
197
|
+
|
|
198
|
+
Uses separate models for path parameters and request body for clean API design.
|
|
199
|
+
|
|
200
|
+
Returns a class with static methods:
|
|
201
|
+
- create_url: Creates URL from path parameters
|
|
202
|
+
- create_payload: Creates request body payload
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
# Verify that the path_params_constructor and payload_constructor are valid pydantic models
|
|
206
|
+
if not dump_options:
|
|
207
|
+
dump_options = {
|
|
208
|
+
"exclude_unset": True,
|
|
209
|
+
"by_alias": True,
|
|
210
|
+
"exclude_defaults": True,
|
|
211
|
+
}
|
|
212
|
+
else:
|
|
213
|
+
warnings.warn(
|
|
214
|
+
"dump_options is deprecated. Use payload_dump_options instead.",
|
|
215
|
+
DeprecationWarning,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
class Operation(ApiOperationProtocol):
|
|
219
|
+
@staticmethod
|
|
220
|
+
def path_dump_options() -> dict[str, Any]:
|
|
221
|
+
return path_dump_options or {}
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def path_params_model() -> type[PathParamsType]:
|
|
225
|
+
return cast(type[PathParamsType], path_params_constructor)
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def payload_dump_options() -> dict[str, Any]:
|
|
229
|
+
return payload_dump_options or {}
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def payload_model() -> type[PayloadType]:
|
|
233
|
+
return cast(type[PayloadType], payload_constructor)
|
|
234
|
+
|
|
235
|
+
@staticmethod
|
|
236
|
+
def query_params_dump_options() -> dict[str, Any]:
|
|
237
|
+
return query_params_dump_options or {}
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def query_params_model() -> type[QueryParamsType]:
|
|
241
|
+
return cast(type[QueryParamsType], query_params_constructor)
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def response_validate_options() -> dict[str, Any]:
|
|
245
|
+
return response_validate_options or {}
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def response_model() -> type[ResponseType]:
|
|
249
|
+
return response_model_type
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def path_template() -> Template:
|
|
253
|
+
return path_template
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
def create_path_from_model(
|
|
257
|
+
path_params: PathParamsType, *, model_dump_options: dict | None = None
|
|
258
|
+
) -> str:
|
|
259
|
+
if model_dump_options is None:
|
|
260
|
+
if path_dump_options is None:
|
|
261
|
+
model_dump_options = dump_options
|
|
262
|
+
else:
|
|
263
|
+
model_dump_options = path_dump_options
|
|
264
|
+
|
|
265
|
+
return path_template.substitute(
|
|
266
|
+
**path_params.model_dump(**model_dump_options)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def create_path(
|
|
271
|
+
*args: PathParamsSpec.args, **kwargs: PathParamsSpec.kwargs
|
|
272
|
+
) -> str:
|
|
273
|
+
model = Operation.path_params_model()(*args, **kwargs)
|
|
274
|
+
return Operation.create_path_from_model(model)
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def create_payload(
|
|
278
|
+
*args: PayloadParamSpec.args,
|
|
279
|
+
**kwargs: PayloadParamSpec.kwargs,
|
|
280
|
+
) -> dict[str, Any]:
|
|
281
|
+
"""Create request body payload."""
|
|
282
|
+
if payload_dump_options is None:
|
|
283
|
+
model_dump_options = dump_options
|
|
284
|
+
else:
|
|
285
|
+
model_dump_options = payload_dump_options
|
|
286
|
+
|
|
287
|
+
request_model = Operation.payload_model()(*args, **kwargs)
|
|
288
|
+
return request_model.model_dump(**model_dump_options)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def create_payload_from_model(
|
|
292
|
+
payload: PayloadType, *, model_dump_options: dict | None = None
|
|
293
|
+
) -> dict[str, Any]:
|
|
294
|
+
if model_dump_options is None:
|
|
295
|
+
if payload_dump_options is None:
|
|
296
|
+
model_dump_options = dump_options
|
|
297
|
+
else:
|
|
298
|
+
model_dump_options = payload_dump_options
|
|
299
|
+
|
|
300
|
+
return payload.model_dump(**model_dump_options)
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def create_query_params(
|
|
304
|
+
*args: QueryParamsSpec.args, **kwargs: QueryParamsSpec.kwargs
|
|
305
|
+
) -> dict[str, Any]:
|
|
306
|
+
if query_params_dump_options is None:
|
|
307
|
+
model_dump_options = dump_options
|
|
308
|
+
else:
|
|
309
|
+
model_dump_options = query_params_dump_options
|
|
310
|
+
|
|
311
|
+
return query_params_constructor(*args, **kwargs).model_dump(
|
|
312
|
+
**model_dump_options
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def create_query_params_from_model(
|
|
317
|
+
query_params: QueryParamsType, *, model_dump_options: dict | None = None
|
|
318
|
+
) -> dict[str, Any]:
|
|
319
|
+
if model_dump_options is None:
|
|
320
|
+
if query_params_dump_options is None:
|
|
321
|
+
model_dump_options = dump_options
|
|
322
|
+
else:
|
|
323
|
+
model_dump_options = query_params_dump_options
|
|
324
|
+
|
|
325
|
+
return query_params.model_dump(**model_dump_options)
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def handle_response(
|
|
329
|
+
response: dict[str, Any],
|
|
330
|
+
*,
|
|
331
|
+
model_validate_options: dict[str, Any] | None = None,
|
|
332
|
+
) -> ResponseType:
|
|
333
|
+
if model_validate_options is None:
|
|
334
|
+
if response_validate_options is None:
|
|
335
|
+
model_validate_options = {}
|
|
336
|
+
else:
|
|
337
|
+
model_validate_options = response_validate_options
|
|
338
|
+
try:
|
|
339
|
+
return Operation.response_model().model_validate(
|
|
340
|
+
response, **model_validate_options
|
|
341
|
+
)
|
|
342
|
+
except ValidationError as e:
|
|
343
|
+
raise ResponseValidationException(
|
|
344
|
+
operation_name=Operation.__name__,
|
|
345
|
+
response=response,
|
|
346
|
+
pydantic_error=str(e),
|
|
347
|
+
) from e
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def request_method() -> HttpMethods:
|
|
351
|
+
return method
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def models_from_combined(
|
|
355
|
+
combined: dict[str, Any],
|
|
356
|
+
) -> tuple[PathParamsType, PayloadType, QueryParamsType]:
|
|
357
|
+
path_params = Operation.path_params_model().model_validate(
|
|
358
|
+
combined, by_alias=True, by_name=True
|
|
359
|
+
)
|
|
360
|
+
payload = Operation.payload_model().model_validate(
|
|
361
|
+
combined, by_alias=True, by_name=True
|
|
362
|
+
)
|
|
363
|
+
query_params = Operation.query_params_model().model_validate(
|
|
364
|
+
combined, by_alias=True, by_name=True
|
|
365
|
+
)
|
|
366
|
+
return path_params, payload, query_params
|
|
367
|
+
|
|
368
|
+
return Operation
|