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.
Files changed (20) hide show
  1. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/.gitignore +3 -0
  2. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/PKG-INFO +1 -1
  3. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/pyproject.toml +5 -5
  4. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/__init__.py +11 -11
  5. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/_utils.py +9 -3
  6. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/document.py +120 -120
  7. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/model.py +204 -11
  8. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/spreadsheet.py +199 -199
  9. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_document_spreadsheet.py +58 -58
  10. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_model.py +69 -0
  11. licos_dev_sdk-0.2.10/tests/test_output_paths.py +60 -0
  12. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/archive.py +0 -0
  13. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/chart.py +0 -0
  14. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/data.py +0 -0
  15. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/diagram.py +0 -0
  16. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/image.py +0 -0
  17. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/observability.py +0 -0
  18. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/presentation.py +0 -0
  19. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/src/licos_dev_sdk/web.py +0 -0
  20. {licos_dev_sdk-0.2.8 → licos_dev_sdk-0.2.10}/tests/test_observability.py +0 -0
@@ -33,6 +33,7 @@ crates/industrial/industrial-stack.env
33
33
  # Build
34
34
  *.log
35
35
  *.pid
36
+ crates/industrial/bin/
36
37
  .licos
37
38
  .tmp
38
39
  .playwright-cli
@@ -42,6 +43,8 @@ tools/android-sdk-cache/*.zip
42
43
 
43
44
  *.codex-*
44
45
 
46
+ __pycache__
47
+
45
48
  dist
46
49
  logs
47
50
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: licos-dev-sdk
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: LICOS Dev SDK - file generation and model capability clients
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: docxtpl>=0.16
@@ -4,16 +4,16 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "licos-dev-sdk"
7
- version = "0.2.8"
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
- env = os.environ.get("LICOS_WORKSPACE_PATH", "")
19
- p = Path(env) if env else Path.cwd()
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())