mirror of
https://github.com/rendercv/rendercv.git
synced 2026-04-19 06:22:56 -04:00
Allow URLs for cv.photo field
This commit is contained in:
@@ -99,14 +99,11 @@ def copy_photo_next_to_typst_file(
|
||||
rendercv_model: CV model containing photo path.
|
||||
typst_path: Path to Typst source file.
|
||||
"""
|
||||
if rendercv_model.cv.photo:
|
||||
photo_path = rendercv_model.cv.photo
|
||||
photo_path = rendercv_model.cv.photo
|
||||
if isinstance(photo_path, pathlib.Path):
|
||||
copy_to = typst_path.parent / photo_path.name
|
||||
if photo_path != copy_to:
|
||||
shutil.copy(
|
||||
rendercv_model.cv.photo,
|
||||
typst_path.parent / rendercv_model.cv.photo.name,
|
||||
)
|
||||
shutil.copy(photo_path, copy_to)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import pathlib
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections.abc import Callable
|
||||
from typing import Literal
|
||||
|
||||
from rendercv.exception import RenderCVUserError
|
||||
from rendercv.schema.models.cv.section import Entry
|
||||
from rendercv.schema.models.rendercv_model import RenderCVModel
|
||||
|
||||
@@ -16,6 +20,44 @@ from .string_processor import (
|
||||
)
|
||||
|
||||
|
||||
def download_photo_from_url(rendercv_model: RenderCVModel) -> None:
|
||||
"""Download photo from URL to output directory and update model to local path.
|
||||
|
||||
Why:
|
||||
Templates and Typst compiler require cv.photo to be a local pathlib.Path.
|
||||
When user provides a URL, this downloads the image before template
|
||||
rendering, preserving the local-path invariant for all downstream code.
|
||||
|
||||
Args:
|
||||
rendercv_model: CV model whose photo URL will be downloaded in-place.
|
||||
"""
|
||||
if rendercv_model.cv.photo is None or isinstance(
|
||||
rendercv_model.cv.photo, pathlib.Path
|
||||
):
|
||||
return
|
||||
|
||||
url_str = str(rendercv_model.cv.photo)
|
||||
|
||||
parsed = urllib.parse.urlparse(url_str)
|
||||
filename = pathlib.PurePosixPath(parsed.path).name
|
||||
if not filename or "." not in filename:
|
||||
filename = "photo.jpg"
|
||||
|
||||
output_dir = rendercv_model.settings.render_command.output_folder
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
destination = output_dir / filename
|
||||
|
||||
if not destination.exists():
|
||||
try:
|
||||
urllib.request.urlretrieve(url_str, destination)
|
||||
except Exception as e:
|
||||
raise RenderCVUserError(
|
||||
message=f"Failed to download photo from {url_str}: {e}"
|
||||
) from e
|
||||
|
||||
rendercv_model.cv.photo = destination
|
||||
|
||||
|
||||
def process_model(
|
||||
rendercv_model: RenderCVModel, file_type: Literal["typst", "markdown"]
|
||||
) -> RenderCVModel:
|
||||
|
||||
@@ -8,7 +8,7 @@ import jinja2
|
||||
from rendercv.schema.models.rendercv_model import RenderCVModel
|
||||
|
||||
from .markdown_parser import markdown_to_html
|
||||
from .model_processor import process_model
|
||||
from .model_processor import download_photo_from_url, process_model
|
||||
from .string_processor import clean_url
|
||||
|
||||
templates_directory = pathlib.Path(__file__).parent / "templates"
|
||||
@@ -79,6 +79,7 @@ def render_full_template(
|
||||
"markdown": "md",
|
||||
}[file_type]
|
||||
|
||||
download_photo_from_url(rendercv_model)
|
||||
rendercv_model = process_model(rendercv_model, file_type)
|
||||
|
||||
header = render_single_template(
|
||||
|
||||
@@ -49,10 +49,11 @@ class Cv(BaseModelWithoutExtraKeys):
|
||||
["john.doe.1@example.com", "john.doe.2@example.com"],
|
||||
],
|
||||
)
|
||||
photo: ExistingPathRelativeToInput | None = pydantic.Field(
|
||||
photo: ExistingPathRelativeToInput | pydantic.HttpUrl | None = pydantic.Field(
|
||||
default=None,
|
||||
description="Photo file path, relative to the YAML file.",
|
||||
examples=["photo.jpg", "images/profile.png"],
|
||||
union_mode="left_to_right",
|
||||
description="Photo file path (relative to the YAML file) or a URL.",
|
||||
examples=["photo.jpg", "images/profile.png", "https://example.com/photo.jpg"],
|
||||
)
|
||||
phone: (
|
||||
pydantic_phone_numbers.PhoneNumber
|
||||
|
||||
@@ -4,7 +4,11 @@ import rendercv_fonts
|
||||
from rendercv.schema.models.design.font_family import available_font_families
|
||||
|
||||
icon_font_families = {"Font Awesome 7"}
|
||||
typst_built_in_font_families = {"Libertinus Serif", "New Computer Modern", "DejaVu Sans Mono"}
|
||||
typst_built_in_font_families = {
|
||||
"Libertinus Serif",
|
||||
"New Computer Modern",
|
||||
"DejaVu Sans Mono",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
Reference in New Issue
Block a user