Skip to content

Commit e3093d2

Browse files
committed
refactor: improve performance
Improves performance by making better use of concurrency for extracting data from Markdown files and processing images
1 parent f5a052a commit e3093d2

File tree

2 files changed

+59
-35
lines changed

2 files changed

+59
-35
lines changed

blurry/__init__.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ def process_non_markdown_file(
6363
):
6464
# Process Jinja files
6565
if ".jinja" in filepath.suffixes:
66-
# Process file
6766
process_jinja_file(filepath, jinja_env, file_data_by_directory)
6867
return
6968

@@ -196,22 +195,31 @@ def gather_file_data_by_directory() -> DirectoryFileData:
196195
file_data_by_directory: DirectoryFileData = {}
197196
content_directory = get_content_directory()
198197

199-
for filepath in content_directory.glob("**/*.md"):
200-
# Extract filepath for storing context data and writing out
201-
relative_filepath = filepath.relative_to(content_directory)
202-
directory = relative_filepath.parent
203-
204-
# Convert Markdown file to HTML
205-
body, front_matter = convert_markdown_file_to_html(filepath)
206-
file_data = MarkdownFileData(
207-
body=body,
208-
front_matter=front_matter,
209-
path=relative_filepath,
210-
)
211-
try:
212-
file_data_by_directory[directory].append(file_data)
213-
except KeyError:
214-
file_data_by_directory[directory] = [file_data]
198+
markdown_future_to_path: dict[concurrent.futures.Future, Path] = {}
199+
with concurrent.futures.ProcessPoolExecutor() as executor:
200+
for filepath in content_directory.rglob("*.md"):
201+
# Extract filepath for storing context data and writing out
202+
relative_filepath = filepath.relative_to(content_directory)
203+
204+
# Convert Markdown file to HTML
205+
future = executor.submit(convert_markdown_file_to_html, filepath)
206+
markdown_future_to_path[future] = relative_filepath
207+
208+
for future in concurrent.futures.as_completed(markdown_future_to_path):
209+
body, front_matter = future.result()
210+
relative_filepath = markdown_future_to_path[future]
211+
file_data = MarkdownFileData(
212+
body=body,
213+
front_matter=front_matter,
214+
path=relative_filepath,
215+
)
216+
parent_directory = relative_filepath.parent
217+
try:
218+
file_data_by_directory[parent_directory].append(file_data)
219+
except KeyError:
220+
file_data_by_directory[parent_directory] = [file_data]
221+
222+
concurrent.futures.wait(markdown_future_to_path)
215223

216224
return sort_directory_file_data_by_date(file_data_by_directory)
217225

blurry/images.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import concurrent.futures
2+
from concurrent.futures import Future
23
from pathlib import Path
34

45
from wand.image import Image
@@ -43,38 +44,53 @@ def convert_image_to_avif(image_path: Path, target_path: Path | None = None):
4344

4445

4546
def clone_and_resize_image(
46-
image: Image, target_width: int, resized_image_destination: Path
47+
image_path: Path, target_width: int, resized_image_destination: Path
4748
):
4849
if resized_image_destination.exists():
4950
return
50-
image.transform(resize=str(target_width))
51-
image.save(filename=resized_image_destination)
51+
with Image(filename=str(image_path)) as image:
52+
image.transform(resize=str(target_width))
53+
image.save(filename=resized_image_destination)
5254

5355

5456
async def generate_images_for_srcset(image_path: Path):
5557
BUILD_DIR = get_build_directory()
5658
CONTENT_DIR = get_content_directory()
57-
filepaths_to_convert_to_avif = []
59+
image_futures: list[Future] = []
5860

5961
build_path = BUILD_DIR / image_path.resolve().relative_to(CONTENT_DIR)
6062
with concurrent.futures.ThreadPoolExecutor() as executor:
61-
with Image(filename=str(image_path)) as img:
62-
width = img.width
63-
64-
for target_width in get_widths_for_image_width(width):
65-
new_filepath = add_image_width_to_path(image_path, target_width)
66-
relative_filepath = new_filepath.resolve().relative_to(CONTENT_DIR)
67-
build_filepath = BUILD_DIR / relative_filepath
68-
# We convert the resized images to AVIF, so do this synchronously
69-
clone_and_resize_image(img, target_width, build_filepath)
70-
filepaths_to_convert_to_avif.append(build_filepath)
71-
7263
# Convert original image
73-
executor.submit(
64+
full_sized_avif_future = executor.submit(
7465
convert_image_to_avif, image_path=image_path, target_path=build_path
7566
)
76-
# Generate AVIF files for resized images
77-
executor.map(convert_image_to_avif, filepaths_to_convert_to_avif)
67+
image_futures.append(full_sized_avif_future)
68+
69+
# Store resized image futures so AVIF versions can be created once they're done
70+
resize_future_to_filepath: dict[Future, Path] = {}
71+
72+
width = Image(filename=str(image_path)).width
73+
74+
for target_width in get_widths_for_image_width(width):
75+
new_filepath = add_image_width_to_path(image_path, target_width)
76+
new_filepath_in_build = BUILD_DIR / new_filepath.relative_to(CONTENT_DIR)
77+
78+
resized_original_image_type_future = executor.submit(
79+
clone_and_resize_image, image_path, target_width, new_filepath_in_build
80+
)
81+
resize_future_to_filepath[
82+
resized_original_image_type_future
83+
] = new_filepath_in_build
84+
85+
# Create AVIF versions of resized images as they're ready
86+
for future in concurrent.futures.as_completed(resize_future_to_filepath):
87+
resized_image_filepath = resize_future_to_filepath[future]
88+
resized_avif_future = executor.submit(
89+
convert_image_to_avif, resized_image_filepath
90+
)
91+
image_futures.append(resized_avif_future)
92+
93+
concurrent.futures.wait(image_futures + list(resize_future_to_filepath.keys()))
7894

7995

8096
def get_widths_for_image_width(image_width: int) -> list[int]:

0 commit comments

Comments
 (0)