licos-dev-sdk 0.2.8__tar.gz → 0.2.10__tar.gz
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.
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/.gitignore +3 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/PKG-INFO +1 -1
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/pyproject.toml +5 -5
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/__init__.py +11 -11
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/_utils.py +9 -3
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/document.py +120 -120
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/model.py +204 -11
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/spreadsheet.py +199 -199
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_document_spreadsheet.py +58 -58
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_model.py +69 -0
- licos_dev_sdk-0.2.10/tests/test_output_paths.py +60 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/archive.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/chart.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/data.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/diagram.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/image.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/observability.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/presentation.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/web.py +0 -0
- {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_observability.py +0 -0
|
@@ -4,16 +4,16 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "licos-dev-sdk"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.10"
|
|
8
8
|
description = "LICOS Dev SDK - file generation and model capability clients"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"licos-platform-sdk>=0.2.8",
|
|
12
12
|
"weasyprint>=62.0",
|
|
13
|
-
"python-docx>=1.1",
|
|
14
|
-
"docxtpl>=0.16",
|
|
15
|
-
"openpyxl>=3.1",
|
|
16
|
-
"xlsxwriter>=3.2",
|
|
13
|
+
"python-docx>=1.1",
|
|
14
|
+
"docxtpl>=0.16",
|
|
15
|
+
"openpyxl>=3.1",
|
|
16
|
+
"xlsxwriter>=3.2",
|
|
17
17
|
"python-pptx>=1.0",
|
|
18
18
|
"matplotlib>=3.9",
|
|
19
19
|
"graphviz>=0.20",
|
|
@@ -23,14 +23,14 @@ def __getattr__(name: str):
|
|
|
23
23
|
"create_html": ("web", "create_html"),
|
|
24
24
|
"create_markdown": ("web", "create_markdown"),
|
|
25
25
|
"markdown_to_html": ("web", "markdown_to_html"),
|
|
26
|
-
# document
|
|
27
|
-
"create_pdf": ("document", "create_pdf"),
|
|
28
|
-
"create_docx": ("document", "create_docx"),
|
|
29
|
-
"create_docx_from_template": ("document", "create_docx_from_template"),
|
|
30
|
-
# spreadsheet
|
|
31
|
-
"create_xlsx": ("spreadsheet", "create_xlsx"),
|
|
32
|
-
"create_xlsx_workbook": ("spreadsheet", "create_xlsx_workbook"),
|
|
33
|
-
"create_csv": ("spreadsheet", "create_csv"),
|
|
26
|
+
# document
|
|
27
|
+
"create_pdf": ("document", "create_pdf"),
|
|
28
|
+
"create_docx": ("document", "create_docx"),
|
|
29
|
+
"create_docx_from_template": ("document", "create_docx_from_template"),
|
|
30
|
+
# spreadsheet
|
|
31
|
+
"create_xlsx": ("spreadsheet", "create_xlsx"),
|
|
32
|
+
"create_xlsx_workbook": ("spreadsheet", "create_xlsx_workbook"),
|
|
33
|
+
"create_csv": ("spreadsheet", "create_csv"),
|
|
34
34
|
# chart
|
|
35
35
|
"create_chart": ("chart", "create_chart"),
|
|
36
36
|
# diagram
|
|
@@ -87,9 +87,9 @@ __all__ = [
|
|
|
87
87
|
"create_json", "create_xml", "create_yaml",
|
|
88
88
|
"create_zip", "create_tar_gz",
|
|
89
89
|
"create_qrcode", "create_barcode", "create_watermark",
|
|
90
|
-
"create_html", "create_markdown", "markdown_to_html",
|
|
91
|
-
"create_pdf", "create_docx", "create_docx_from_template",
|
|
92
|
-
"create_xlsx", "create_xlsx_workbook", "create_csv",
|
|
90
|
+
"create_html", "create_markdown", "markdown_to_html",
|
|
91
|
+
"create_pdf", "create_docx", "create_docx_from_template",
|
|
92
|
+
"create_xlsx", "create_xlsx_workbook", "create_csv",
|
|
93
93
|
"create_chart",
|
|
94
94
|
"create_diagram",
|
|
95
95
|
"create_pptx",
|
|
@@ -10,13 +10,19 @@ from pathlib import Path
|
|
|
10
10
|
def resolve_output_dir(output_dir: str | None = None) -> Path:
|
|
11
11
|
"""Resolve the output directory.
|
|
12
12
|
|
|
13
|
-
Priority: explicit arg > $LICOS_WORKSPACE_PATH > cwd
|
|
13
|
+
Priority: explicit arg > $LICOS_PROJECT_PATH > $LICOS_WORKSPACE_PATH/projects > cwd
|
|
14
14
|
"""
|
|
15
15
|
if output_dir:
|
|
16
16
|
p = Path(output_dir)
|
|
17
17
|
else:
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
project_path = os.environ.get("LICOS_PROJECT_PATH", "")
|
|
19
|
+
workspace_path = os.environ.get("LICOS_WORKSPACE_PATH", "")
|
|
20
|
+
if project_path:
|
|
21
|
+
p = Path(project_path)
|
|
22
|
+
elif workspace_path:
|
|
23
|
+
p = Path(workspace_path) / "projects"
|
|
24
|
+
else:
|
|
25
|
+
p = Path.cwd()
|
|
20
26
|
p.mkdir(parents=True, exist_ok=True)
|
|
21
27
|
return p
|
|
22
28
|
|
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
"""Document generation — PDF, DOCX."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from docx import Document
|
|
8
|
-
from docx.shared import Pt, Inches
|
|
9
|
-
|
|
10
|
-
from ._utils import resolve_output_path
|
|
11
|
-
|
|
12
|
-
_PDF_CSS = """
|
|
13
|
-
@page { size: {page_size}; margin: 2cm; }
|
|
14
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans CJK SC", sans-serif;
|
|
15
|
-
font-size: 11pt; line-height: 1.6; color: #333; }
|
|
16
|
-
h1 { font-size: 22pt; margin-top: 0; }
|
|
17
|
-
h2 { font-size: 16pt; }
|
|
18
|
-
h3 { font-size: 13pt; }
|
|
19
|
-
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
20
|
-
th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; }
|
|
21
|
-
th { background: #f0f0f0; font-weight: 600; }
|
|
22
|
-
code { background: #f4f4f4; padding: 1px 4px; border-radius: 3px; font-size: 10pt; }
|
|
23
|
-
pre { background: #f4f4f4; padding: 12px; border-radius: 4px; overflow-x: auto; }
|
|
24
|
-
blockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 1em; color: #666; }
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def create_pdf(content: str, filename: str, *, content_type: str = "markdown",
|
|
29
|
-
output_dir: str | None = None, page_size: str = "A4") -> str:
|
|
30
|
-
"""Generate a PDF file from Markdown or HTML. Returns absolute path."""
|
|
31
|
-
import mistune
|
|
32
|
-
from weasyprint import HTML
|
|
33
|
-
|
|
34
|
-
path = resolve_output_path(filename, ".pdf", output_dir)
|
|
35
|
-
html_body = mistune.html(content) if content_type == "markdown" else content
|
|
36
|
-
css = _PDF_CSS.replace("{page_size}", page_size)
|
|
37
|
-
full_html = f"<html><head><style>{css}</style></head><body>{html_body}</body></html>"
|
|
38
|
-
HTML(string=full_html).write_pdf(str(path))
|
|
39
|
-
return str(path.resolve())
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def create_docx(content: str, filename: str, *, content_type: str = "markdown",
|
|
43
|
-
output_dir: str | None = None, font_name: str = "Arial", font_size: int = 11) -> str:
|
|
44
|
-
"""Generate a DOCX file from Markdown or HTML. Returns absolute path.
|
|
45
|
-
|
|
46
|
-
Converts Markdown to a simple DOCX with headings, paragraphs, and lists.
|
|
47
|
-
For complex HTML, consider generating PDF instead.
|
|
48
|
-
"""
|
|
49
|
-
path = resolve_output_path(filename, ".docx", output_dir)
|
|
50
|
-
|
|
51
|
-
if content_type == "html":
|
|
52
|
-
# For HTML input, convert to markdown-like text first
|
|
53
|
-
import re
|
|
54
|
-
text = re.sub(r"<[^>]+>", "", content)
|
|
55
|
-
lines = [l.strip() for l in text.split("\n") if l.strip()]
|
|
56
|
-
else:
|
|
57
|
-
lines = content.split("\n")
|
|
58
|
-
|
|
59
|
-
doc = Document()
|
|
60
|
-
# Set default font
|
|
61
|
-
style = doc.styles["Normal"]
|
|
62
|
-
style.font.name = font_name
|
|
63
|
-
style.font.size = Pt(font_size)
|
|
64
|
-
|
|
65
|
-
for line in lines:
|
|
66
|
-
stripped = line.strip()
|
|
67
|
-
if not stripped:
|
|
68
|
-
continue
|
|
69
|
-
# Headings
|
|
70
|
-
if stripped.startswith("######"):
|
|
71
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=6)
|
|
72
|
-
elif stripped.startswith("#####"):
|
|
73
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=5)
|
|
74
|
-
elif stripped.startswith("####"):
|
|
75
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=4)
|
|
76
|
-
elif stripped.startswith("###"):
|
|
77
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=3)
|
|
78
|
-
elif stripped.startswith("##"):
|
|
79
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=2)
|
|
80
|
-
elif stripped.startswith("#"):
|
|
81
|
-
doc.add_heading(stripped.lstrip("#").strip(), level=1)
|
|
82
|
-
# Unordered list
|
|
83
|
-
elif stripped.startswith("- ") or stripped.startswith("* "):
|
|
84
|
-
doc.add_paragraph(stripped[2:], style="List Bullet")
|
|
85
|
-
# Ordered list
|
|
86
|
-
elif len(stripped) > 2 and stripped[0].isdigit() and stripped[1] in (".", ")"):
|
|
87
|
-
doc.add_paragraph(stripped[2:].strip(), style="List Number")
|
|
88
|
-
# Blockquote
|
|
89
|
-
elif stripped.startswith("> "):
|
|
90
|
-
p = doc.add_paragraph(stripped[2:])
|
|
91
|
-
p.paragraph_format.left_indent = Inches(0.5)
|
|
92
|
-
p.style.font.italic = True
|
|
93
|
-
# Horizontal rule
|
|
94
|
-
elif stripped in ("---", "***", "___"):
|
|
95
|
-
doc.add_paragraph("─" * 50)
|
|
96
|
-
else:
|
|
97
|
-
doc.add_paragraph(stripped)
|
|
98
|
-
|
|
99
|
-
doc.save(str(path))
|
|
100
|
-
return str(path.resolve())
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def create_docx_from_template(template_path: str, data: dict[str, Any], filename: str, *,
|
|
104
|
-
output_dir: str | None = None) -> str:
|
|
105
|
-
"""Render a DOCX template with Jinja-style placeholders.
|
|
106
|
-
|
|
107
|
-
The template must be a `.docx` file. Placeholders use docxtpl syntax, for example
|
|
108
|
-
`{{ customer_name }}` or `{% for item in items %}...{% endfor %}`.
|
|
109
|
-
Returns the absolute path of the generated document.
|
|
110
|
-
"""
|
|
111
|
-
if not isinstance(data, dict):
|
|
112
|
-
raise TypeError("data must be a dict")
|
|
113
|
-
|
|
114
|
-
from docxtpl import DocxTemplate
|
|
115
|
-
|
|
116
|
-
path = resolve_output_path(filename, ".docx", output_dir)
|
|
117
|
-
doc = DocxTemplate(template_path)
|
|
118
|
-
doc.render(data)
|
|
119
|
-
doc.save(str(path))
|
|
120
|
-
return str(path.resolve())
|
|
1
|
+
"""Document generation — PDF, DOCX."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from docx import Document
|
|
8
|
+
from docx.shared import Pt, Inches
|
|
9
|
+
|
|
10
|
+
from ._utils import resolve_output_path
|
|
11
|
+
|
|
12
|
+
_PDF_CSS = """
|
|
13
|
+
@page { size: {page_size}; margin: 2cm; }
|
|
14
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans CJK SC", sans-serif;
|
|
15
|
+
font-size: 11pt; line-height: 1.6; color: #333; }
|
|
16
|
+
h1 { font-size: 22pt; margin-top: 0; }
|
|
17
|
+
h2 { font-size: 16pt; }
|
|
18
|
+
h3 { font-size: 13pt; }
|
|
19
|
+
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
20
|
+
th, td { border: 1px solid #ccc; padding: 6px 10px; text-align: left; }
|
|
21
|
+
th { background: #f0f0f0; font-weight: 600; }
|
|
22
|
+
code { background: #f4f4f4; padding: 1px 4px; border-radius: 3px; font-size: 10pt; }
|
|
23
|
+
pre { background: #f4f4f4; padding: 12px; border-radius: 4px; overflow-x: auto; }
|
|
24
|
+
blockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 1em; color: #666; }
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_pdf(content: str, filename: str, *, content_type: str = "markdown",
|
|
29
|
+
output_dir: str | None = None, page_size: str = "A4") -> str:
|
|
30
|
+
"""Generate a PDF file from Markdown or HTML. Returns absolute path."""
|
|
31
|
+
import mistune
|
|
32
|
+
from weasyprint import HTML
|
|
33
|
+
|
|
34
|
+
path = resolve_output_path(filename, ".pdf", output_dir)
|
|
35
|
+
html_body = mistune.html(content) if content_type == "markdown" else content
|
|
36
|
+
css = _PDF_CSS.replace("{page_size}", page_size)
|
|
37
|
+
full_html = f"<html><head><style>{css}</style></head><body>{html_body}</body></html>"
|
|
38
|
+
HTML(string=full_html).write_pdf(str(path))
|
|
39
|
+
return str(path.resolve())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_docx(content: str, filename: str, *, content_type: str = "markdown",
|
|
43
|
+
output_dir: str | None = None, font_name: str = "Arial", font_size: int = 11) -> str:
|
|
44
|
+
"""Generate a DOCX file from Markdown or HTML. Returns absolute path.
|
|
45
|
+
|
|
46
|
+
Converts Markdown to a simple DOCX with headings, paragraphs, and lists.
|
|
47
|
+
For complex HTML, consider generating PDF instead.
|
|
48
|
+
"""
|
|
49
|
+
path = resolve_output_path(filename, ".docx", output_dir)
|
|
50
|
+
|
|
51
|
+
if content_type == "html":
|
|
52
|
+
# For HTML input, convert to markdown-like text first
|
|
53
|
+
import re
|
|
54
|
+
text = re.sub(r"<[^>]+>", "", content)
|
|
55
|
+
lines = [l.strip() for l in text.split("\n") if l.strip()]
|
|
56
|
+
else:
|
|
57
|
+
lines = content.split("\n")
|
|
58
|
+
|
|
59
|
+
doc = Document()
|
|
60
|
+
# Set default font
|
|
61
|
+
style = doc.styles["Normal"]
|
|
62
|
+
style.font.name = font_name
|
|
63
|
+
style.font.size = Pt(font_size)
|
|
64
|
+
|
|
65
|
+
for line in lines:
|
|
66
|
+
stripped = line.strip()
|
|
67
|
+
if not stripped:
|
|
68
|
+
continue
|
|
69
|
+
# Headings
|
|
70
|
+
if stripped.startswith("######"):
|
|
71
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=6)
|
|
72
|
+
elif stripped.startswith("#####"):
|
|
73
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=5)
|
|
74
|
+
elif stripped.startswith("####"):
|
|
75
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=4)
|
|
76
|
+
elif stripped.startswith("###"):
|
|
77
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=3)
|
|
78
|
+
elif stripped.startswith("##"):
|
|
79
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=2)
|
|
80
|
+
elif stripped.startswith("#"):
|
|
81
|
+
doc.add_heading(stripped.lstrip("#").strip(), level=1)
|
|
82
|
+
# Unordered list
|
|
83
|
+
elif stripped.startswith("- ") or stripped.startswith("* "):
|
|
84
|
+
doc.add_paragraph(stripped[2:], style="List Bullet")
|
|
85
|
+
# Ordered list
|
|
86
|
+
elif len(stripped) > 2 and stripped[0].isdigit() and stripped[1] in (".", ")"):
|
|
87
|
+
doc.add_paragraph(stripped[2:].strip(), style="List Number")
|
|
88
|
+
# Blockquote
|
|
89
|
+
elif stripped.startswith("> "):
|
|
90
|
+
p = doc.add_paragraph(stripped[2:])
|
|
91
|
+
p.paragraph_format.left_indent = Inches(0.5)
|
|
92
|
+
p.style.font.italic = True
|
|
93
|
+
# Horizontal rule
|
|
94
|
+
elif stripped in ("---", "***", "___"):
|
|
95
|
+
doc.add_paragraph("─" * 50)
|
|
96
|
+
else:
|
|
97
|
+
doc.add_paragraph(stripped)
|
|
98
|
+
|
|
99
|
+
doc.save(str(path))
|
|
100
|
+
return str(path.resolve())
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def create_docx_from_template(template_path: str, data: dict[str, Any], filename: str, *,
|
|
104
|
+
output_dir: str | None = None) -> str:
|
|
105
|
+
"""Render a DOCX template with Jinja-style placeholders.
|
|
106
|
+
|
|
107
|
+
The template must be a `.docx` file. Placeholders use docxtpl syntax, for example
|
|
108
|
+
`{{ customer_name }}` or `{% for item in items %}...{% endfor %}`.
|
|
109
|
+
Returns the absolute path of the generated document.
|
|
110
|
+
"""
|
|
111
|
+
if not isinstance(data, dict):
|
|
112
|
+
raise TypeError("data must be a dict")
|
|
113
|
+
|
|
114
|
+
from docxtpl import DocxTemplate
|
|
115
|
+
|
|
116
|
+
path = resolve_output_path(filename, ".docx", output_dir)
|
|
117
|
+
doc = DocxTemplate(template_path)
|
|
118
|
+
doc.render(data)
|
|
119
|
+
doc.save(str(path))
|
|
120
|
+
return str(path.resolve())
|