Skip to content

Add interactive .ics calendar download page into documentation#3282

Merged
arkid15r merged 33 commits intovacanza:devfrom
pareshjoshij:feat/3168-ics-download-portal
Mar 25, 2026
Merged

Add interactive .ics calendar download page into documentation#3282
arkid15r merged 33 commits intovacanza:devfrom
pareshjoshij:feat/3168-ics-download-portal

Conversation

@pareshjoshij
Copy link
Copy Markdown
Contributor

@pareshjoshij pareshjoshij commented Feb 19, 2026

Proposed change

This PR introduces an interactive documentation portal allowing users to preview and download custom .ics holiday calendars.

Key Updates:

  • Frontend Portal: Adds an Alpine.js-powered download page (downloads.md, downloads.js) with filtering and local-first data fetching.
  • Asset Generation: Introduces scripts/generate_site_assets.py for parallelized, automated generation of .ics and .json files.
  • CI/CD Integration: Adds a GitHub Pages workflow (deploy-gh-pages.yml) to automatically build and serve the new calendar assets.

Part of #woc
Closes #3168

Type of change

  • New feature (new holidays functionality in general)
  • Existing code/documentation/test/process quality improvement

Copilot AI review requested due to automatic review settings February 19, 2026 15:03
@pareshjoshij pareshjoshij marked this pull request as draft February 19, 2026 15:03
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new site asset generator script and a language registry, ignores generated downloads in Git, and adds a lint-ignore for the new script. The generator produces ICS and per-year inline JSON assets plus per-entity manifests and a top-level index.json under docs/downloads/ics.

Changes

Cohort / File(s) Summary
Asset generation script
scripts/generate_site_assets.py
New script that discovers countries and financial markets, generates ICS and inline JSON per entity/year/subdivision, parallelizes processing, writes per-entity manifests and a top-level index.json, and defines DEFAULT_YEAR_START, DEFAULT_YEAR_END, OUTPUT_DIR, write_assets, process_entity, and main.
Language registry
scripts/language_registry.py
New module exposing LANGUAGES — a mapping of locale codes to human-readable language names (many locales added).
Repo config
.gitignore, pyproject.toml
.gitignore: added docs/downloads to ignore generated artifacts. pyproject.toml: added per-file lint-ignore entry for scripts/generate_site_assets.py (suppresses T201).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR introduces the foundational .ics generation automation (#3168, #3163), but does not yet implement the frontend portal or CI/CD integration mentioned as main tasks. Clarify whether this PR is scoped only to backend .ics generation, or if frontend and CI/CD automation are expected in this changeset.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed All changes (generate_site_assets.py, language_registry.py, .gitignore, pyproject.toml updates) directly support .ics file generation automation without unrelated modifications.
Description check ✅ Passed The PR description clearly outlines the introduction of an interactive documentation portal with frontend, asset generation, and CI/CD components, which aligns well with the changeset.
Title check ✅ Passed The PR title directly aligns with the main objective—adding infrastructure to support downloading .ics calendar files for the documentation site.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/generate_site_assets.py`:
- Around line 127-128: The dict construction unnecessarily wraps iterables in
list() before calling sorted(); change the entries that read "languages":
sorted(list(languages)) and "categories": sorted(list(categories)) to simply use
sorted(languages) and sorted(categories) respectively (update the code where the
dict is built so the sorted() calls accept the iterables directly).
- Around line 98-99: The docstring for function process_entity contains a typo
("Worker for Generates assets"); update it to a correct, concise phrasing such
as "Worker that generates assets for a single Country/Market." to fix grammar
and clarity in the process_entity docstring.
- Around line 177-183: The DEV branch currently hardcodes output_dir =
Path("docs/downloads/ics") and ignores the --output flag; modify the block under
if args.dev to use args.output (e.g., set output_dir = Path(args.output) or
respect args.output when present) before calling clean_output_dir(output_dir),
so args.output is honored in DEV mode; keep the rest of the DEV overrides
(target_years, target_countries, target_financial) unchanged and only replace
the hardcoded Path with the args.output-derived Path.
- Around line 35-39: The clean_output_dir(path) function currently calls
shutil.rmtree on a user-supplied path with no validation; update it to validate
the path before deleting by ensuring the resolved path is a subpath of the
project root (or check for a specific sentinel file inside the directory) and
refuse to operate if the path resolves to a filesystem root or home directory
(e.g., path.resolve() == Path("/") or path.resolve() == Path.home()) or is
outside the project directory; if validation fails, raise a clear exception
instead of deleting, otherwise proceed to rmtree and recreate the directory.
- Around line 147-148: Replace the silent "except Exception: pass" blocks with
proper exception logging: catch the exception as e (e.g., "except Exception as
e") and call the module logger (or logger.exception) to record the error plus
contextual identifiers such as entity, year, language in the first block and
subdivision in the second block so you can trace which item failed; after
logging, continue to the next iteration to preserve existing behavior. Ensure
you reference the existing loop variables (entity, year, language, subdivision)
in the log message and use logger.exception or logger.error with traceback for
full diagnostics.
- Around line 217-221: The as_completed loop currently calls future.result()
directly which will re-raise any exceptions from process_entity and crash before
the manifest is written; wrap the future.result() call in a try/except inside
the loop (catch Exception), log the error and the future identity (or the input
entity), and continue so other futures still contribute to manifest; ensure you
do not lose partial results by leaving manifest population as-is and allowing
the final manifest write to run even if some futures failed (or explicitly write
a partial manifest in the except branch) — update the loop around futures and
the exception handling to reference the futures/as_completed loop and
process_entity results accordingly.
- Around line 113-118: Set default_lang from instance.default_language before
computing languages, then change the languages fallback to use that default
rather than hardcoding ["en"]; specifically, move the default_lang assignment
above where languages is defined and replace the languages fallback expression
(instance.supported_languages if instance.supported_languages else ["en"]) with
(instance.supported_languages if instance.supported_languages else
[default_lang]); also remove any hardcoded "Thailand" or "en_US" example logic
related to supported languages so the fallback uniformly uses default_lang;
leave subdiv_codes and categories logic unchanged.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new site-asset generation script intended to produce downloadable holiday calendars (.ics) plus JSON previews/manifests for supported countries and financial markets.

Changes:

  • Introduces scripts/generate_site_assets.py to generate per-entity/per-year .ics and .json files.
  • Adds parallelized processing via ProcessPoolExecutor to speed up generation.
  • Produces an index.json manifest containing entity metadata (languages, categories, subdivisions).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pareshjoshij
Copy link
Copy Markdown
Contributor Author

@KJhellico @PPsyrius 👀

Copy link
Copy Markdown
Collaborator

@KJhellico KJhellico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General suggestion: simpler is better. :)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 16 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/generate_site_assets.py`:
- Around line 117-118: The loop that calls write_assets is silently skipping
ChildEntity objects when h_obj.subdiv != subdiv; add a diagnostic log inside
that branch to record when a h_obj is skipped including h_obj identifier (e.g.,
h_obj.subdiv or h_obj.code/name), the expected subdiv value, and context (lang,
cat, year_dir) so developers can trace why files weren’t generated; locate the
check around the call to write_assets (the conditional using h_obj.subdiv ==
subdiv) and insert a processLogger or logging.debug/info line that includes
those symbols.

---

Duplicate comments:
In `@scripts/generate_site_assets.py`:
- Around line 67-96: Wrap the entire body of the worker function
(process_entity) in a top-level try/except to prevent uncaught exceptions from
escaping executor.map: catch Exception around all metadata extraction and file
operations (including calls to get_subdivision_aliases(), reading
supported_categories/supported_languages/default_language, and
year_dir.mkdir()), log the error with context (code, name, year) and return a
safe value (e.g., None or a dict indicating failure) so the main executor.map
iteration can continue and the manifest-writing logic still runs; alternatively,
add similar exception handling around the executor.map consumption in main() to
catch worker exceptions, log them, and continue processing.
- Around line 71-73: The fallback language is computed after languages, so
languages uses the hardcoded ["en_US"] instead of the instance.default_language;
compute default_lang first (use getattr(instance, "default_language", "en_US"))
and then set languages = instance.supported_languages if
instance.supported_languages else [default_lang], leaving categories =
instance.supported_categories unchanged and updating any references to
languages/default_lang accordingly.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/generate_site_assets.py`:
- Around line 126-153: The current main() uses
ProcessPoolExecutor().map(process_entity, work_items) but if a worker raises an
uncaught exception the map will propagate and stop iteration losing partial
manifest; wrap the for result in executor.map(...) loop in a try/except to catch
any Exception (or BaseException for extreme cases like MemoryError), log the
error, and continue/flush the manifest (i.e., ensure manifest writing still
runs). Specifically update the loop in main() that iterates over executor.map to
handle and recover from exceptions raised during iteration so
manifest[etype][code] entries already collected are preserved and written out.
- Around line 32-48: The function write_assets currently returns early when
h_obj is falsy, skipping creation of .ics/.json files; instead, handle empty
HolidayBase by always generating files: remove the early return and ensure you
still instantiate ICalExporter(h_obj) and call exporter.save_ics(...) for an
empty calendar, and write an empty-but-valid JSON (e.g. "[]") to (year_dir /
f"{filename_base}.json"). Keep the try/except around exporter.save_ics and the
JSON write so errors are caught, and use filename_base and year_dir as in the
existing code paths.
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01155c0 and 823552d.

📒 Files selected for processing (2)
  • .gitignore
  • scripts/generate_site_assets.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
scripts/generate_site_assets.py (3)

140-144: ⚠️ Potential issue | 🟡 Minor

executor.map propagates worker exceptions and aborts iteration — partial manifest is lost.

If any process_entity call raises an exception that escapes its internal try/except (e.g., MemoryError, pickle failure), executor.map re-raises it, stopping the loop before index.json is written. The previously flagged (optional) fix still applies.

🛡️ Defensive wrap
     with ProcessPoolExecutor() as executor:
-        for result in executor.map(process_entity, work_items):
-            if result:
-                etype, code, meta = result
-                manifest[etype][code] = meta
+        try:
+            for result in executor.map(process_entity, work_items):
+                if result:
+                    etype, code, meta = result
+                    manifest[etype][code] = meta
+        except Exception as e:
+            print(f"Fatal worker error; writing partial manifest. ({e})")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_site_assets.py` around lines 140 - 144, executor.map will
re-raise worker exceptions and abort the loop, losing a partially-built
manifest; change the pattern around ProcessPoolExecutor so that you submit tasks
with executor.submit(process_entity, item) and iterate over
concurrent.futures.as_completed(futures) to collect results into manifest,
catching and logging exceptions per-future (so MemoryError/pickle errors don't
stop processing) and still write out index.json from the partially-populated
manifest; update references to executor.map -> executor.submit/as_completed,
keep using process_entity and manifest, and ensure any raised exceptions are
logged but do not stop other tasks from being processed.

130-132: ⚠️ Potential issue | 🟠 Major

shutil.rmtree on a hardcoded-but-overridable path still lacks a safety check.

If OUTPUT_DIR is ever misconfigured or the script is adapted to accept an external path, this call nukes the tree with no validation. The past review proposed requiring the path to remain under the project root.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_site_assets.py` around lines 130 - 132, Before calling
shutil.rmtree(OUTPUT_DIR) add a safety check that verifies OUTPUT_DIR.resolve()
is a subpath of the project root and is not a dangerous absolute path (e.g., "/"
or home). Use the project root Path (e.g., PROJECT_ROOT or derive with
Path(__file__).resolve().parents[n]) and assert
OUTPUT_DIR.resolve().is_relative_to(PROJECT_ROOT.resolve()) (or compare path
parts/prefix if Python <3.9), and raise an error or skip removal if the check
fails; only then call shutil.rmtree(OUTPUT_DIR) and proceed to
OUTPUT_DIR.mkdir(...).

34-35: ⚠️ Potential issue | 🟡 Minor

Empty holiday objects still silently skipped.

if not h_obj: return short-circuits before writing any file when there are zero holidays for a year/lang/category/subdiv combo. If the portal expects a file to always exist per combination, this will produce broken download links. The past review flagged this; consider writing an empty-but-valid .ics instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate_site_assets.py` around lines 34 - 35, The current early
return on "if not h_obj: return" silently skips generating files for
combinations with zero holidays; change this so that when h_obj is empty you
instead build and write an empty-but-valid .ics (a VCALENDAR with no VEVENTs)
using the same writer path used for non-empty h_obj outputs (i.e., the code that
writes holiday .ics files), so download links always have a file; keep the same
filename/metadata and only omit VEVENT entries when h_obj is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@scripts/generate_site_assets.py`:
- Around line 140-144: executor.map will re-raise worker exceptions and abort
the loop, losing a partially-built manifest; change the pattern around
ProcessPoolExecutor so that you submit tasks with
executor.submit(process_entity, item) and iterate over
concurrent.futures.as_completed(futures) to collect results into manifest,
catching and logging exceptions per-future (so MemoryError/pickle errors don't
stop processing) and still write out index.json from the partially-populated
manifest; update references to executor.map -> executor.submit/as_completed,
keep using process_entity and manifest, and ensure any raised exceptions are
logged but do not stop other tasks from being processed.
- Around line 130-132: Before calling shutil.rmtree(OUTPUT_DIR) add a safety
check that verifies OUTPUT_DIR.resolve() is a subpath of the project root and is
not a dangerous absolute path (e.g., "/" or home). Use the project root Path
(e.g., PROJECT_ROOT or derive with Path(__file__).resolve().parents[n]) and
assert OUTPUT_DIR.resolve().is_relative_to(PROJECT_ROOT.resolve()) (or compare
path parts/prefix if Python <3.9), and raise an error or skip removal if the
check fails; only then call shutil.rmtree(OUTPUT_DIR) and proceed to
OUTPUT_DIR.mkdir(...).
- Around line 34-35: The current early return on "if not h_obj: return" silently
skips generating files for combinations with zero holidays; change this so that
when h_obj is empty you instead build and write an empty-but-valid .ics (a
VCALENDAR with no VEVENTs) using the same writer path used for non-empty h_obj
outputs (i.e., the code that writes holiday .ics files), so download links
always have a file; keep the same filename/metadata and only omit VEVENT entries
when h_obj is empty.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 823552d and 6f6d44c.

📒 Files selected for processing (2)
  • pyproject.toml
  • scripts/generate_site_assets.py

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (e0dc6f9) to head (916b72e).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff            @@
##               dev     #3282   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          310       310           
  Lines        18584     18584           
  Branches      2380      2380           
=========================================
  Hits         18584     18584           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pareshjoshij pareshjoshij requested a review from KJhellico March 17, 2026 17:32
@pareshjoshij
Copy link
Copy Markdown
Contributor Author

@KJhellico sir , Is this looking good to go now, or do you still need me to make some changes?

Copy link
Copy Markdown
Collaborator

@KJhellico KJhellico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this can be considered a working version.

@pareshjoshij pareshjoshij requested a review from KJhellico March 21, 2026 18:53
PPsyrius
PPsyrius previously approved these changes Mar 22, 2026
Copy link
Copy Markdown
Collaborator

@PPsyrius PPsyrius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ProperDocs migration looks good, LGTM 🗓️

generate_site_asset could surely be refactor to speedup a bit, but let's handle that in a future PR 🎉

@KJhellico KJhellico changed the title Add page for downloading .ics files Add interactive .ics calendar download page into documenation Mar 22, 2026
KJhellico
KJhellico previously approved these changes Mar 22, 2026
Copy link
Copy Markdown
Collaborator

@KJhellico KJhellico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 👍

@KJhellico KJhellico changed the title Add interactive .ics calendar download page into documenation Add interactive .ics calendar download page into documentation Mar 22, 2026
@arkid15r arkid15r dismissed stale reviews from KJhellico and PPsyrius via 1f40a45 March 25, 2026 19:45
@arkid15r arkid15r enabled auto-merge March 25, 2026 19:46
@sonarqubecloud
Copy link
Copy Markdown

@arkid15r arkid15r added this pull request to the merge queue Mar 25, 2026
Merged via the queue into vacanza:dev with commit f537eb0 Mar 25, 2026
32 checks passed
@pareshjoshij
Copy link
Copy Markdown
Contributor Author

@arkid15r , @KJhellico , @PPsyrius
Thanks so much for the approval! I learned a lot and really enjoyed the process. Sorry again for missing my proposed timeline due to personal reasons, but I'm happy I managed to finish the full task . Thanks for being so patient with me ❤️

@pareshjoshij pareshjoshij deleted the feat/3168-ics-download-portal branch March 27, 2026 06:03
arkid15r added a commit to arkid15r/python-holidays that referenced this pull request Mar 28, 2026
…za#3282)

Co-authored-by: ~Jhellico <KJhellico@users.noreply.github.com>
Co-authored-by: Arkadii Yakovets <ark@cho.red>
Co-authored-by: Arkadii Yakovets <2201626+arkid15r@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a page for downloading .ics files

6 participants