Sequences¶
A tutorial step: a day knows the days either side of it, so a reader can step along the run they came through without going back to the menu.
Wire the day into its run¶
The days-to-come menu is a sequence, and a page reached from it should let the
reader step to the next and the previous — D and A — as well as back. A page
does not track that itself: the session does, and hands it to the page as
request.neighbours. The day page passes those to its PageLayout, names what the
sequence steps between with item_noun, and now says how far off the day is. Its
whole self, the file’s other pages unchanged around it:
import calendar
from collections.abc import AsyncIterator, Callable
from contextlib import asynccontextmanager
from datetime import UTC, date, datetime, timedelta
from sextile import (
Lines, MenuItem, Page, PageLayout, PageRequest, PageRouter, Sextile, StateKey,
keyed, keys, menu_page, notice_page, prose_page,
)
SERVICE_NAME = "CALENDAR"
DAYS_AHEAD = 28
type Clock = Callable[[], datetime]
CLOCK = StateKey[Clock]("clock")
router = PageRouter()
def _now(request: PageRequest) -> datetime:
return request.state[CLOCK]()
def _today(request: PageRequest) -> date:
return _now(request).date()
def _long_date(day: date) -> str:
return day.strftime("%A %d %B %Y")
def _month_name(day: date) -> str:
return day.strftime("%B %Y")
def _in_words(gap: timedelta) -> str:
days = gap.days
if days == 0:
return "today"
if days == 1:
return "tomorrow"
if days == -1:
return "yesterday"
if days > 0:
return f"in {days} days"
return f"{-days} days ago"
@router.page("1", name="main", title="The index", keywords=("MAIN", "INDEX"))
async def main(request: PageRequest) -> Page:
app = request.app
return menu_page(
request,
title=SERVICE_NAME,
preamble=[_long_date(_today(request))],
items=[app.menu_item("now"), app.menu_item("ahead"), app.menu_item("about")],
)
@router.page(
"2", name="now", title="The time now", detail="to the second", keywords=("TIME", "NOW"),
)
async def now_page(request: PageRequest) -> Page:
moment = _now(request)
return notice_page(
request,
_long_date(moment.date()),
"",
moment.strftime("%H:%M:%S"),
moment.tzname() or "",
"",
f"Key {keyed(keys.REFRESH)} to ask again.",
)
@router.page(
"4", name="ahead", title="The days to come",
detail=f"the next {DAYS_AHEAD}", keywords=("AHEAD",),
)
async def ahead(request: PageRequest) -> Page:
app = request.app
today = _today(request)
days = [today + timedelta(days=offset) for offset in range(DAYS_AHEAD)]
return menu_page(
request,
items=[
MenuItem(_long_date(day), _in_words(day - today), app.address_for("day", day=day))
for day in days
],
)
@router.page("42{day:date}", name="day", title="One day")
async def one_day(request: PageRequest, day: date) -> Page:
_, week, _ = day.isocalendar()
length = 366 if calendar.isleap(day.year) else 365
lines = [
_long_date(day),
"",
f"Day {day.timetuple().tm_yday} of {length}",
f"Week {week}",
f"ISO {day.isoformat()}",
"",
_in_words(day - _today(request)),
]
return PageLayout(
title=_month_name(day),
neighbours=request.neighbours,
item_noun="day",
parts=[Lines(lines)],
).build(request)
@router.page("9", name="about", title="About this service", keywords=("ABOUT", "HELP"))
async def about(request: PageRequest) -> Page:
return prose_page(
request,
"A calendar, served as Viewdata frames.",
"It exists to demonstrate that Sextile is a framework and not one "
"service: nothing here knows about forums, and nothing in the framework "
"knows about calendars.",
"Everything it shows comes from the standard library.",
)
def build_application(now: Clock | None = None) -> Sextile:
reading = now or (lambda: datetime.now(UTC))
@asynccontextmanager
async def lifespan(app: Sextile) -> AsyncIterator[None]:
app.state[CLOCK] = reading
yield
return Sextile(name=SERVICE_NAME, pages=[*router], lifespan=lifespan)
app = build_application(now=lambda: datetime(2026, 8, 1, tzinfo=UTC))
August 2026 4220260802a Sunday 02 August 2026 Day 214 of 365 Week 31 ISO 2026-08-02 tomorrow A previous day, D next day, 0 index
The frame above is the second day of the list, reached by keying 2 from it. Its
footer offers A and D to step to the day before and after, because it was
reached through a sequence; a day reached by its number alone offers neither, there
being no run to step along. item_noun="day" is why the footer says so in days.