simpdf 0.1.0__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.
simpdf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: simpdf
3
+ Version: 0.1.0
4
+ Summary: Markdown-to-PDF rendering with FPDF2 and configurable TTF fonts.
5
+ Author: Dmitry
6
+ Keywords: markdown,pdf,fpdf,cyrillic
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: fpdf2<3,>=2.8.0
17
+ Requires-Dist: markdown-it-py<4,>=3.0.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest<9,>=8.0.0; extra == "dev"
20
+
21
+ # simpdf
22
+
23
+ `simpdf` is a small Python library for rendering Markdown into PDF with `fpdf2`, while keeping font handling explicit so Cyrillic and other non-Latin text work reliably with external TTF fonts.
24
+
25
+ The library is centered on a `MarkdownPdfRenderer` class. You give it a directory with TTF files, describe the font face to register, and then render Markdown into PDF bytes or directly into a file.
26
+
27
+ ## Features
28
+
29
+ - Uses `fpdf2` as the PDF backend
30
+ - Supports external TTF fonts and Cyrillic-friendly fonts such as DejaVu Sans
31
+ - Provides a helper to download DejaVu Sans fonts into a target directory
32
+ - Class-first API with optional convenience helpers
33
+ - Minimal CLI for rendering Markdown and downloading DejaVu fonts
34
+ - Supports these Markdown elements in v1:
35
+ - headings
36
+ - paragraphs
37
+ - bold and italic text
38
+ - inline code
39
+ - ordered and unordered lists
40
+ - tables
41
+ - blockquotes
42
+ - fenced code blocks
43
+ - thematic breaks
44
+ - clickable links
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install simpdf
50
+ ```
51
+
52
+ For development and tests:
53
+
54
+ ```bash
55
+ pip install -e .[dev]
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```python
61
+ from simpdf import FontFace, MarkdownPdfRenderer
62
+
63
+ renderer = MarkdownPdfRenderer(
64
+ font_directory="fonts",
65
+ font_face=FontFace.dejavu_sans(),
66
+ )
67
+
68
+ markdown_text = """
69
+ # Example
70
+
71
+ Привет, мир.
72
+
73
+ - one
74
+ - two
75
+ """
76
+
77
+ pdf_bytes = renderer.render_to_bytes(markdown_text)
78
+
79
+ with open("output.pdf", "wb") as handle:
80
+ handle.write(pdf_bytes)
81
+ ```
82
+
83
+ ## Downloading DejaVu Fonts
84
+
85
+ The library does not bundle fonts inside the wheel. Use the download helper to populate your own font directory.
86
+
87
+ ```python
88
+ from simpdf import download_dejavu_fonts
89
+
90
+ downloaded = download_dejavu_fonts("fonts")
91
+ print([path.name for path in downloaded])
92
+ ```
93
+
94
+ The helper downloads the DejaVu font files from this public GitHub repository layout:
95
+
96
+ - `https://github.com/shwars/simpdf/raw/refs/heads/main/fonts/DejaVuSans.ttf`
97
+ - other font files use the same URL pattern
98
+
99
+ ## Using Custom Fonts
100
+
101
+ You can use any TTF family as long as you provide at least a regular face. Bold, italic, and bold-italic are optional; if omitted, the regular face is reused.
102
+
103
+ ```python
104
+ from simpdf import FontFace, MarkdownPdfRenderer
105
+
106
+ renderer = MarkdownPdfRenderer(
107
+ font_directory="my-fonts",
108
+ font_face=FontFace(
109
+ family="NotoSansCustom",
110
+ regular="NotoSans-Regular.ttf",
111
+ bold="NotoSans-Bold.ttf",
112
+ italic="NotoSans-Italic.ttf",
113
+ bold_italic="NotoSans-BoldItalic.ttf",
114
+ ),
115
+ )
116
+ ```
117
+
118
+ Font file values may be file names relative to `font_directory` or absolute paths.
119
+
120
+ ## Formatting Options
121
+
122
+ Formatting is configured with a plain nested dictionary. Any omitted value falls back to the library defaults.
123
+
124
+ Example:
125
+
126
+ ```python
127
+ from simpdf import FontFace, MarkdownPdfRenderer
128
+
129
+ renderer = MarkdownPdfRenderer(
130
+ font_directory="fonts",
131
+ font_face=FontFace.dejavu_sans(),
132
+ formatting_options={
133
+ "text": {"font_size": 11},
134
+ "headings": {
135
+ "sizes": {1: 26, 2: 20, 3: 16},
136
+ },
137
+ "lists": {"indent": 9},
138
+ "table": {"heading_font_size": 13},
139
+ },
140
+ )
141
+ ```
142
+
143
+ Supported option groups:
144
+
145
+ - `page`: page size, orientation, and margins
146
+ - `text`: base font size, line height multiplier, text color
147
+ - `headings`: per-level sizes and spacing
148
+ - `paragraph`: paragraph spacing
149
+ - `lists`: indent, bullet symbol, list spacing
150
+ - `blockquote`: indent, bar styling, text color
151
+ - `table`: font sizes, padding, minimum column width, spacing
152
+ - `code_block`: font size, padding, colors, spacing
153
+ - `inline_code`: inline code text color
154
+ - `links`: link color and underline toggle
155
+ - `thematic_break`: rule color, width, spacing
156
+
157
+ ## Convenience Helpers
158
+
159
+ If you prefer a functional call site, `simpdf` also exports:
160
+
161
+ - `render_markdown_to_pdf_bytes(...)`
162
+ - `render_markdown_to_pdf_file(...)`
163
+
164
+ These helpers internally construct `MarkdownPdfRenderer`.
165
+
166
+ ## CLI Usage
167
+
168
+ Render Markdown into PDF:
169
+
170
+ ```bash
171
+ simpdf render input.md output.pdf \
172
+ --fonts-dir ./fonts \
173
+ --family-name DejaVuSans \
174
+ --font-regular DejaVuSans.ttf \
175
+ --font-bold DejaVuSans-Bold.ttf \
176
+ --font-italic DejaVuSans-Oblique.ttf \
177
+ --font-bold-italic DejaVuSans-BoldOblique.ttf
178
+ ```
179
+
180
+ Download DejaVu fonts:
181
+
182
+ ```bash
183
+ simpdf download-dejavu ./fonts
184
+ ```
185
+
186
+ If you want custom formatting from the CLI, pass a JSON file with `--options-file`.
187
+
188
+ ## Examples
189
+
190
+ See [`examples/basic_render.py`](/D:/GIT/simpdf/examples/basic_render.py), [`examples/custom_font_and_style.py`](/D:/GIT/simpdf/examples/custom_font_and_style.py), and [`examples/rich_markdown.py`](/D:/GIT/simpdf/examples/rich_markdown.py).
191
+
192
+ ## Tests
193
+
194
+ The repo now contains a pytest suite that covers:
195
+
196
+ - font config validation
197
+ - DejaVu download helper behavior
198
+ - markdown token flattening and table extraction
199
+ - PDF rendering with Cyrillic content
200
+ - formatting overrides
201
+ - CLI render and download flows
202
+
203
+ Run tests with:
204
+
205
+ ```bash
206
+ pytest
207
+ ```
208
+
209
+ ## Compatibility Note
210
+
211
+ For older code that imported `simpdf.pdfgen`, a small compatibility wrapper is still present. The recommended API is the class-based renderer from `simpdf`.
simpdf-0.1.0/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # simpdf
2
+
3
+ `simpdf` is a small Python library for rendering Markdown into PDF with `fpdf2`, while keeping font handling explicit so Cyrillic and other non-Latin text work reliably with external TTF fonts.
4
+
5
+ The library is centered on a `MarkdownPdfRenderer` class. You give it a directory with TTF files, describe the font face to register, and then render Markdown into PDF bytes or directly into a file.
6
+
7
+ ## Features
8
+
9
+ - Uses `fpdf2` as the PDF backend
10
+ - Supports external TTF fonts and Cyrillic-friendly fonts such as DejaVu Sans
11
+ - Provides a helper to download DejaVu Sans fonts into a target directory
12
+ - Class-first API with optional convenience helpers
13
+ - Minimal CLI for rendering Markdown and downloading DejaVu fonts
14
+ - Supports these Markdown elements in v1:
15
+ - headings
16
+ - paragraphs
17
+ - bold and italic text
18
+ - inline code
19
+ - ordered and unordered lists
20
+ - tables
21
+ - blockquotes
22
+ - fenced code blocks
23
+ - thematic breaks
24
+ - clickable links
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install simpdf
30
+ ```
31
+
32
+ For development and tests:
33
+
34
+ ```bash
35
+ pip install -e .[dev]
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from simpdf import FontFace, MarkdownPdfRenderer
42
+
43
+ renderer = MarkdownPdfRenderer(
44
+ font_directory="fonts",
45
+ font_face=FontFace.dejavu_sans(),
46
+ )
47
+
48
+ markdown_text = """
49
+ # Example
50
+
51
+ Привет, мир.
52
+
53
+ - one
54
+ - two
55
+ """
56
+
57
+ pdf_bytes = renderer.render_to_bytes(markdown_text)
58
+
59
+ with open("output.pdf", "wb") as handle:
60
+ handle.write(pdf_bytes)
61
+ ```
62
+
63
+ ## Downloading DejaVu Fonts
64
+
65
+ The library does not bundle fonts inside the wheel. Use the download helper to populate your own font directory.
66
+
67
+ ```python
68
+ from simpdf import download_dejavu_fonts
69
+
70
+ downloaded = download_dejavu_fonts("fonts")
71
+ print([path.name for path in downloaded])
72
+ ```
73
+
74
+ The helper downloads the DejaVu font files from this public GitHub repository layout:
75
+
76
+ - `https://github.com/shwars/simpdf/raw/refs/heads/main/fonts/DejaVuSans.ttf`
77
+ - other font files use the same URL pattern
78
+
79
+ ## Using Custom Fonts
80
+
81
+ You can use any TTF family as long as you provide at least a regular face. Bold, italic, and bold-italic are optional; if omitted, the regular face is reused.
82
+
83
+ ```python
84
+ from simpdf import FontFace, MarkdownPdfRenderer
85
+
86
+ renderer = MarkdownPdfRenderer(
87
+ font_directory="my-fonts",
88
+ font_face=FontFace(
89
+ family="NotoSansCustom",
90
+ regular="NotoSans-Regular.ttf",
91
+ bold="NotoSans-Bold.ttf",
92
+ italic="NotoSans-Italic.ttf",
93
+ bold_italic="NotoSans-BoldItalic.ttf",
94
+ ),
95
+ )
96
+ ```
97
+
98
+ Font file values may be file names relative to `font_directory` or absolute paths.
99
+
100
+ ## Formatting Options
101
+
102
+ Formatting is configured with a plain nested dictionary. Any omitted value falls back to the library defaults.
103
+
104
+ Example:
105
+
106
+ ```python
107
+ from simpdf import FontFace, MarkdownPdfRenderer
108
+
109
+ renderer = MarkdownPdfRenderer(
110
+ font_directory="fonts",
111
+ font_face=FontFace.dejavu_sans(),
112
+ formatting_options={
113
+ "text": {"font_size": 11},
114
+ "headings": {
115
+ "sizes": {1: 26, 2: 20, 3: 16},
116
+ },
117
+ "lists": {"indent": 9},
118
+ "table": {"heading_font_size": 13},
119
+ },
120
+ )
121
+ ```
122
+
123
+ Supported option groups:
124
+
125
+ - `page`: page size, orientation, and margins
126
+ - `text`: base font size, line height multiplier, text color
127
+ - `headings`: per-level sizes and spacing
128
+ - `paragraph`: paragraph spacing
129
+ - `lists`: indent, bullet symbol, list spacing
130
+ - `blockquote`: indent, bar styling, text color
131
+ - `table`: font sizes, padding, minimum column width, spacing
132
+ - `code_block`: font size, padding, colors, spacing
133
+ - `inline_code`: inline code text color
134
+ - `links`: link color and underline toggle
135
+ - `thematic_break`: rule color, width, spacing
136
+
137
+ ## Convenience Helpers
138
+
139
+ If you prefer a functional call site, `simpdf` also exports:
140
+
141
+ - `render_markdown_to_pdf_bytes(...)`
142
+ - `render_markdown_to_pdf_file(...)`
143
+
144
+ These helpers internally construct `MarkdownPdfRenderer`.
145
+
146
+ ## CLI Usage
147
+
148
+ Render Markdown into PDF:
149
+
150
+ ```bash
151
+ simpdf render input.md output.pdf \
152
+ --fonts-dir ./fonts \
153
+ --family-name DejaVuSans \
154
+ --font-regular DejaVuSans.ttf \
155
+ --font-bold DejaVuSans-Bold.ttf \
156
+ --font-italic DejaVuSans-Oblique.ttf \
157
+ --font-bold-italic DejaVuSans-BoldOblique.ttf
158
+ ```
159
+
160
+ Download DejaVu fonts:
161
+
162
+ ```bash
163
+ simpdf download-dejavu ./fonts
164
+ ```
165
+
166
+ If you want custom formatting from the CLI, pass a JSON file with `--options-file`.
167
+
168
+ ## Examples
169
+
170
+ See [`examples/basic_render.py`](/D:/GIT/simpdf/examples/basic_render.py), [`examples/custom_font_and_style.py`](/D:/GIT/simpdf/examples/custom_font_and_style.py), and [`examples/rich_markdown.py`](/D:/GIT/simpdf/examples/rich_markdown.py).
171
+
172
+ ## Tests
173
+
174
+ The repo now contains a pytest suite that covers:
175
+
176
+ - font config validation
177
+ - DejaVu download helper behavior
178
+ - markdown token flattening and table extraction
179
+ - PDF rendering with Cyrillic content
180
+ - formatting overrides
181
+ - CLI render and download flows
182
+
183
+ Run tests with:
184
+
185
+ ```bash
186
+ pytest
187
+ ```
188
+
189
+ ## Compatibility Note
190
+
191
+ For older code that imported `simpdf.pdfgen`, a small compatibility wrapper is still present. The recommended API is the class-based renderer from `simpdf`.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "simpdf"
7
+ version = "0.1.0"
8
+ description = "Markdown-to-PDF rendering with FPDF2 and configurable TTF fonts."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [
12
+ {name = "Dmitry"}
13
+ ]
14
+ dependencies = [
15
+ "fpdf2>=2.8.0,<3",
16
+ "markdown-it-py>=3.0.0,<4",
17
+ ]
18
+ keywords = ["markdown", "pdf", "fpdf", "cyrillic"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=8.0.0,<9",
32
+ ]
33
+
34
+ [project.scripts]
35
+ simpdf = "simpdf.cli:main"
36
+
37
+ [tool.setuptools]
38
+ include-package-data = false
39
+
40
+ [tool.setuptools.packages.find]
41
+ include = ["simpdf*"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+ addopts = "-q"
simpdf-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from .fonts import DEJAVU_FONT_FILES, FontFace, download_dejavu_fonts
2
+ from .renderer import MarkdownPdfRenderer, render_markdown_to_pdf_bytes, render_markdown_to_pdf_file
3
+
4
+ __all__ = [
5
+ "DEJAVU_FONT_FILES",
6
+ "FontFace",
7
+ "MarkdownPdfRenderer",
8
+ "download_dejavu_fonts",
9
+ "render_markdown_to_pdf_bytes",
10
+ "render_markdown_to_pdf_file",
11
+ ]
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from .fonts import FontFace, download_dejavu_fonts
8
+ from .renderer import MarkdownPdfRenderer
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(prog="simpdf", description="Render Markdown into PDF with configurable TTF fonts.")
13
+ subparsers = parser.add_subparsers(dest="command", required=True)
14
+
15
+ render_parser = subparsers.add_parser("render", help="Render a Markdown file into PDF.")
16
+ render_parser.add_argument("input_markdown", type=Path)
17
+ render_parser.add_argument("output_pdf", type=Path)
18
+ render_parser.add_argument("--fonts-dir", type=Path, required=True)
19
+ render_parser.add_argument("--family-name", default="DejaVuSans")
20
+ render_parser.add_argument("--font-regular", required=True)
21
+ render_parser.add_argument("--font-bold")
22
+ render_parser.add_argument("--font-italic")
23
+ render_parser.add_argument("--font-bold-italic")
24
+ render_parser.add_argument("--options-file", type=Path, help="Path to a JSON file with formatting options.")
25
+
26
+ download_parser = subparsers.add_parser("download-dejavu", help="Download the DejaVu Sans font files.")
27
+ download_parser.add_argument("target_dir", type=Path)
28
+ download_parser.add_argument("--overwrite", action="store_true")
29
+
30
+ return parser
31
+
32
+
33
+ def main(argv: list[str] | None = None) -> int:
34
+ parser = build_parser()
35
+ args = parser.parse_args(argv)
36
+
37
+ if args.command == "download-dejavu":
38
+ download_dejavu_fonts(args.target_dir, overwrite=args.overwrite)
39
+ return 0
40
+
41
+ options = {}
42
+ if args.options_file:
43
+ options = json.loads(args.options_file.read_text(encoding="utf-8"))
44
+
45
+ renderer = MarkdownPdfRenderer(
46
+ font_directory=args.fonts_dir,
47
+ font_face=FontFace(
48
+ family=args.family_name,
49
+ regular=args.font_regular,
50
+ bold=args.font_bold,
51
+ italic=args.font_italic,
52
+ bold_italic=args.font_bold_italic,
53
+ ),
54
+ formatting_options=options,
55
+ )
56
+ markdown_text = args.input_markdown.read_text(encoding="utf-8")
57
+ renderer.render_to_file(markdown_text, args.output_pdf)
58
+ return 0
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Mapping
6
+ from urllib.request import urlopen
7
+
8
+
9
+ DEJAVU_BASE_URL = "https://github.com/shwars/simpdf/raw/refs/heads/main/fonts"
10
+ DEJAVU_FONT_FILES = (
11
+ "DejaVuSans.ttf",
12
+ "DejaVuSans-Bold.ttf",
13
+ "DejaVuSans-Oblique.ttf",
14
+ "DejaVuSans-BoldOblique.ttf",
15
+ "DejaVuSansCondensed.ttf",
16
+ "DejaVuSansCondensed-Bold.ttf",
17
+ "DejaVuSansCondensed-Oblique.ttf",
18
+ "DejaVuSansCondensed-BoldOblique.ttf",
19
+ )
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class FontFace:
24
+ family: str
25
+ regular: str
26
+ bold: str | None = None
27
+ italic: str | None = None
28
+ bold_italic: str | None = None
29
+
30
+ @classmethod
31
+ def dejavu_sans(cls) -> "FontFace":
32
+ return cls(
33
+ family="DejaVuSans",
34
+ regular="DejaVuSans.ttf",
35
+ bold="DejaVuSans-Bold.ttf",
36
+ italic="DejaVuSans-Oblique.ttf",
37
+ bold_italic="DejaVuSans-BoldOblique.ttf",
38
+ )
39
+
40
+
41
+ def coerce_font_face(font_face: FontFace | Mapping[str, str]) -> FontFace:
42
+ if isinstance(font_face, FontFace):
43
+ return font_face
44
+
45
+ try:
46
+ family = font_face["family"]
47
+ regular = font_face["regular"]
48
+ except KeyError as exc:
49
+ raise ValueError(f"Missing required font face field: {exc.args[0]}") from exc
50
+
51
+ return FontFace(
52
+ family=family,
53
+ regular=regular,
54
+ bold=font_face.get("bold"),
55
+ italic=font_face.get("italic"),
56
+ bold_italic=font_face.get("bold_italic"),
57
+ )
58
+
59
+
60
+ def resolve_font_paths(font_directory: str | Path, font_face: FontFace | Mapping[str, str]) -> dict[str, Path]:
61
+ directory = Path(font_directory)
62
+ face = coerce_font_face(font_face)
63
+ if not directory.exists():
64
+ raise FileNotFoundError(f"Font directory does not exist: {directory}")
65
+
66
+ regular = _resolve_path(directory, face.regular)
67
+ bold = _resolve_path(directory, face.bold) if face.bold else regular
68
+ italic = _resolve_path(directory, face.italic) if face.italic else regular
69
+ bold_italic = _resolve_path(directory, face.bold_italic) if face.bold_italic else bold
70
+
71
+ missing = [path for path in (regular, bold, italic, bold_italic) if not path.exists()]
72
+ if missing:
73
+ message = ", ".join(str(path) for path in missing)
74
+ raise FileNotFoundError(f"Missing required font files: {message}")
75
+
76
+ return {
77
+ "regular": regular,
78
+ "bold": bold,
79
+ "italic": italic,
80
+ "bold_italic": bold_italic,
81
+ }
82
+
83
+
84
+ def register_font_family(pdf, font_directory: str | Path, font_face: FontFace | Mapping[str, str]) -> str:
85
+ face = coerce_font_face(font_face)
86
+ resolved = resolve_font_paths(font_directory, face)
87
+ pdf.add_font(face.family, style="", fname=str(resolved["regular"]))
88
+ pdf.add_font(face.family, style="B", fname=str(resolved["bold"]))
89
+ pdf.add_font(face.family, style="I", fname=str(resolved["italic"]))
90
+ pdf.add_font(face.family, style="BI", fname=str(resolved["bold_italic"]))
91
+ return face.family
92
+
93
+
94
+ def download_dejavu_fonts(
95
+ target_dir: str | Path,
96
+ *,
97
+ overwrite: bool = False,
98
+ timeout: int = 30,
99
+ ) -> list[Path]:
100
+ target = Path(target_dir)
101
+ target.mkdir(parents=True, exist_ok=True)
102
+ downloaded: list[Path] = []
103
+
104
+ for filename in DEJAVU_FONT_FILES:
105
+ destination = target / filename
106
+ if destination.exists() and not overwrite:
107
+ downloaded.append(destination)
108
+ continue
109
+
110
+ url = f"{DEJAVU_BASE_URL}/{filename}"
111
+ with urlopen(url, timeout=timeout) as response:
112
+ destination.write_bytes(response.read())
113
+ downloaded.append(destination)
114
+
115
+ return downloaded
116
+
117
+
118
+ def _resolve_path(base_dir: Path, value: str) -> Path:
119
+ path = Path(value)
120
+ if path.is_absolute():
121
+ return path
122
+ return base_dir / path