Skip to content

Menu System

jsem-nerad edited this page Sep 1, 2026 · 3 revisions

Menu System

The menu is reached as strava.menu. It holds the canteen's published menu as Day objects, each holding Meal objects.

Fetching

Nothing is loaded until you ask:

strava.menu.fetch()

fetch() returns the menu, so you can chain:

days = strava.menu.fetch().get_days()

Using the menu before fetching raises rather than quietly returning an empty list:

from strava_cz import MenuNotFetchedError

try:
    strava.menu.get_days()
except MenuNotFetchedError:
    strava.menu.fetch()

strava.menu.fetched tells you which state you are in.

Re-fetching replaces everything, and the library does it automatically after every ordering operation so the local copy never drifts from the canteen's.

The Meal object

meal = strava.menu.get_by_id(5)
attribute type meaning
id int Identifier used for ordering (the API's veta)
date datetime.date The day it is served
type MealType SOUP, MAIN or UNKNOWN
variant str The canteen's label, e.g. "Oběd 1"
name str What is being served
price float Price in the account's currency
ordered bool Whether it is currently ordered
can_order bool Whether it can be ordered right now
can_cancel bool Whether an existing order can be cancelled
order_restriction Restriction Why it cannot be ordered
cancel_restriction Restriction Why it cannot be cancelled
allergens tuple[Allergen, ...] (code, name) pairs
deadline datetime | None When ordering closes
raw dict The untouched API record

Meals are frozen dataclasses: you can compare them, put them in sets, and you cannot change them by accident.

print(meal)        # [5] Kuřecí kostky na paprice (50) - not ordered
print(meal.name, meal.price, meal.date.strftime("%d.%m."))

for code, name in meal.allergens:
    print(f"{code}: {name}")

raw is the escape hatch. Anything the library does not model is still there:

print(meal.raw["alergeny_text"])
print(meal.raw["omezeniObj"])

The Day object

attribute type meaning
date datetime.date The day
meals tuple[Meal, ...] Its meals, in the order the API returned them
ordered bool Whether at least one meal is ordered
orderable tuple[Meal, ...] The meals that can be ordered right now
no_school bool Whether the canteen does not cook that day
auto_ordered bool Whether the canteen orders that day for you
status DayStatus What the canteen says about the day as a whole
day_code str The raw day-level code (omezeniObj.den)
day = strava.menu.get_by_date("2026-09-07")

print(day)                       # 2026-09-07 (3 meals)
print(len(day))                  # 3
for meal in day:                 # days iterate over their meals
    print(meal.name)

Filtering

get_days() returns days, get_meals() returns the same selection flattened. Both take the same keyword arguments, and all of them default to "do not filter".

strava.menu.get_days(
    meal_types=None,        # e.g. [MealType.MAIN]
    orderable=None,         # True / False
    ordered=None,           # True / False
    auto_ordered=None,      # True / False - picks whole days
    date_from=None,         # date or "YYYY-MM-DD"
    date_to=None,           # inclusive
    include_no_school=False,
)

Nothing is hidden by default except days the canteen does not cook on. Every published meal comes back, and each one tells you whether it can be ordered. This is a deliberate change from v0.2.0, whose default silently dropped meals it classified as restricted — and misclassified about a quarter of them.

from strava_cz import MealType

# Everything
all_meals = strava.menu.get_meals()

# Just what you can order now
orderable = strava.menu.get_meals(orderable=True)

# Just what you cannot, to find out why
for meal in strava.menu.get_meals(orderable=False):
    print(meal.name, "-", meal.order_restriction.description)

# The days nothing arrives on unless you order it yourself
for day in strava.menu.get_days(auto_ordered=False):
    print(day.date, "- order it yourself or go hungry")

# Main dishes only
lunches = strava.menu.get_meals(meal_types=[MealType.MAIN])

# Already ordered
mine = strava.menu.get_meals(ordered=True)

# A date range
week = strava.menu.get_days(date_from="2026-09-07", date_to="2026-09-11")

# Filters combine
choices = strava.menu.get_meals(
    meal_types=[MealType.MAIN], orderable=True, ordered=False, date_from="2026-09-07"
)

Days that end up with no matching meals drop out of the result, so get_days(meal_types=[MealType.SOUP], orderable=True) is empty in a canteen where soup is never separately orderable.

Days with no school

Holidays and closure days are excluded unless you ask for them. They carry no meals — the canteen fills those slots with placeholder rows such as "Nevaří se", which the library does not present as food:

closed = [d for d in strava.menu.get_days(include_no_school=True) if d.no_school]
for day in closed:
    print(f"No school on {day.date}")

Lookups

strava.menu.get_by_id(5)              # Meal, or None
strava.menu.get_by_date("2026-09-07") # Day, or None
strava.menu.get_by_date(datetime.date(2026, 9, 7))
strava.menu.is_ordered(5)             # bool; raises MealNotFoundError for unknown ids
5 in strava.menu                      # bool, never raises

is_ordered() raising for an unknown id is intentional: silently answering False for an id that does not exist hides typos and stale ids.

Sequence behaviour

Menu behaves like the list of days you would get from get_days():

len(strava.menu)          # number of days with meals
strava.menu[0]            # first Day
strava.menu[:3]           # first three days
for day in strava.menu:
    ...
repr(strava.menu)         # Menu(days=20, meals=57, orderable=36)

Printing

strava.menu.print()
strava.menu.print(orderable=True)                    # takes the same filters
strava.menu.print(meal_types=[MealType.MAIN])

Raw data and parse warnings

strava.menu.raw_data          # the decoded objednavky response, untouched
strava.menu.parse_warnings    # rows the parser could not use

A row with an unreadable date or a shape the parser does not recognise is skipped and recorded in parse_warnings rather than taking the whole fetch down. If you see warnings, the API has probably changed — please open an issue.

Clone this wiki locally