Skip to content

Commit

Permalink
Add some metadata to calendars
Browse files Browse the repository at this point in the history
  • Loading branch information
RealOrangeOne committed Apr 24, 2024
1 parent c1338b2 commit 613a368
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 16 deletions.
2 changes: 1 addition & 1 deletion calmerge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def get_aiohttp_app(config: Config) -> web.Application:
app.add_routes(
[
web.get("/.health/", views.healthcheck),
web.get("/{name}.ics", views.calendar),
web.get("/{slug}.ics", views.calendar),
]
)

Expand Down
2 changes: 1 addition & 1 deletion calmerge/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def write_calendars(args: argparse.Namespace) -> None:
config = Config.from_file(args.config)

for calendar_config in config.calendars:
output_file = output_dir / f"{calendar_config.name}.ics"
output_file = output_dir / f"{calendar_config.slug}.ics"
print("Saving", output_file)
write_calendar(calendar_config, output_file)

Expand Down
14 changes: 14 additions & 0 deletions calmerge/calendars.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,17 @@ def create_offset_calendar_events(

for component in new_components:
calendar.add_component(component)


def set_calendar_metadata(
calendar: icalendar.Calendar, calendar_config: CalendarConfig
) -> None:
"""
Mutate a calendar to set metadata based on config
"""

if calendar_config.name:
calendar.add("X-WR-CALNAME", calendar_config.name)

if calendar_config.description:
calendar.add("X-WR-CALDESC", calendar_config.description)
8 changes: 5 additions & 3 deletions calmerge/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def validate_header(self, auth_header: str) -> bool:


class CalendarConfig(BaseModel):
name: str
slug: str
name: str | None = None
description: str | None = None
urls: list[HttpUrl]
offset_days: list[int] = Field(default_factory=list)
auth: AuthConfig | None = None
Expand Down Expand Up @@ -82,7 +84,7 @@ def from_file(cls, path: Path) -> "Config":
with path.open(mode="rb") as f:
return Config.model_validate(tomllib.load(f))

def get_calendar_by_name(self, name: str) -> CalendarConfig | None:
def get_calendar_by_slug(self, slug: str) -> CalendarConfig | None:
return next(
(calendar for calendar in self.calendars if calendar.name == name), None
(calendar for calendar in self.calendars if calendar.slug == slug), None
)
8 changes: 7 additions & 1 deletion calmerge/static.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import asyncio
from pathlib import Path

from .calendars import create_offset_calendar_events, fetch_merged_calendar
from .calendars import (
create_offset_calendar_events,
fetch_merged_calendar,
set_calendar_metadata,
)
from .config import CalendarConfig


Expand All @@ -11,4 +15,6 @@ def write_calendar(calendar_config: CalendarConfig, output_file: Path) -> None:
if offset_days := calendar_config.offset_days:
create_offset_calendar_events(merged_calendar, offset_days)

set_calendar_metadata(merged_calendar, calendar_config)

output_file.write_bytes(merged_calendar.to_ical())
10 changes: 8 additions & 2 deletions calmerge/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from aiohttp import web

from .calendars import create_offset_calendar_events, fetch_merged_calendar
from .calendars import (
create_offset_calendar_events,
fetch_merged_calendar,
set_calendar_metadata,
)


async def healthcheck(request: web.Request) -> web.Response:
Expand All @@ -10,7 +14,7 @@ async def healthcheck(request: web.Request) -> web.Response:
async def calendar(request: web.Request) -> web.Response:
config = request.app["config"]

calendar_config = config.get_calendar_by_name(request.match_info["name"])
calendar_config = config.get_calendar_by_slug(request.match_info["slug"])

if calendar_config is None:
raise web.HTTPNotFound()
Expand All @@ -25,4 +29,6 @@ async def calendar(request: web.Request) -> web.Response:
if offset_days := calendar_config.offset_days:
create_offset_calendar_events(calendar, offset_days)

set_calendar_metadata(calendar, calendar_config)

return web.Response(body=calendar.to_ical())
12 changes: 9 additions & 3 deletions tests/calendars.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
[[calendar]]
name = "python"
slug = "python"
name = "Python"
description = "Python EOL"
urls = [
"https://endoflife.date/calendar/python.ics",
]

[[calendar]]
name = "python-offset"
slug = "python-offset"
name = "Python (with offset)"
description = "Python EOL (with offset)"
urls = [
"https://endoflife.date/calendar/python.ics",
]
offset_days = [365]

[[calendar]]
name = "python-authed"
slug = "python-authed"
name = "Python (with auth)"
description = "Python EOL (with auth)"
urls = [
"https://endoflife.date/calendar/python.ics",
]
Expand Down
3 changes: 3 additions & 0 deletions tests/test_calendar_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ async def test_retrieves_calendars(client: TestClient) -> None:
calendar = icalendar.Calendar.from_ical(await response.text())
assert not calendar.is_broken

assert calendar["X-WR-CALNAME"] == "Python"
assert calendar["X-WR-CALDESC"] == "Python EOL"


async def test_unknown_calendar(client: TestClient) -> None:
response = await client.get("/unknown.ics")
Expand Down
8 changes: 4 additions & 4 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@

def test_non_unique_urls() -> None:
with pytest.raises(ValidationError) as e:
CalendarConfig(name="test", urls=["https://example.com"] * 10) # type: ignore [list-item]
CalendarConfig(slug="test", urls=["https://example.com"] * 10) # type: ignore [list-item]

assert e.value.errors()[0]["msg"] == "URLs must be unique"


def test_non_unique_offset_days() -> None:
with pytest.raises(ValidationError) as e:
CalendarConfig(
name="test",
slug="test",
urls=["https://example.com"], # type: ignore [list-item]
offset_days=[1, 2, 3, 2, 1],
)
Expand All @@ -31,7 +31,7 @@ def test_non_unique_offset_days() -> None:
def test_invalid_offset_days() -> None:
with pytest.raises(ValidationError) as e:
CalendarConfig(
name="test",
slug="test",
urls=["https://example.com"], # type: ignore [list-item]
offset_days=[MAX_OFFSET + 1],
)
Expand All @@ -46,7 +46,7 @@ def test_urls_expand_env_var(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("FOO", "BAR")

calendar_config = CalendarConfig(
name="test",
slug="test",
urls=["https://example.com/$FOO"], # type: ignore [list-item]
)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ def test_write_config(tmp_path: Path, config: Config, config_path: Path) -> None
assert len(list(tmp_path.glob("*.ics"))) == len(config.calendars)

for calendar_config in config.calendars:
assert tmp_path.joinpath(f"{calendar_config.name}.ics").is_file()
assert tmp_path.joinpath(f"{calendar_config.slug}.ics").is_file()

0 comments on commit 613a368

Please sign in to comment.