skip to Main Content

I’m developing telegram bot. My bot helps users to remember their things to do. So the thing is there are many types of such things to do which user can create: everyday, every week, few times a day, once a year, etc. My bot has a command which shows user’s records. To do it properly I wrote a function which makes text of the answer (e.g. if type is "everyday" then bot answers "everyday at HH:MM", if type is "every week" then bot says "every monday at HH:MM").

My problem is that to do it I made a dictionary where keys are types of such records, values are text which bot might tell user. But this dictionary doesn’t work correct: when I pass record type "month" or "everyday" or any other to the function it always get value of key "week" although it should get value of key "month" or "everyday". I get KeyError besause of it

Code:
Main function with the command to show records:

@dp.message_handler(commands=['reminders'], state="*")
async def show_reminder(message: types.Message):
    """
    Shows user's records
    """
    records = get_records(int(message.from_user.id))
    if records:
        await message.answer(messages.show_records_message)
        for item in records:
            await message.answer(make_record_text(item))
    else:
        ###

The function which creates text of the answer:

def make_record_text(record: Record) -> str:
    """
    Creates an answer with user's record using Record
    """
    days_of_week_dict = {
        'monday': "every monday",
        'tuesday': "every tuesday",
        'wednesday': "every wednesday",
        'thursday': "every thursday",
        'friday': "every friday",
        'saturday': "every saturday",
        'sunday': "every sunday",
    }
    print(f'record = {record}')
    print(f'record type = {record.type}')
    print(f'record date = {record.date}')
    print(f'record time = {record.time}')

    periods = {'year': f'every year {record.time}',
               'once': record.date,
               'week': f'{days_of_week_dict[record.time]}',
               'month': f'every month on {record.time}',
               'several_min': f'every {record.time} minutes',
               'everyday': f'everyday at {record.time}',
               'several_hours': f'every {record.time} hours',
               'every_few_days': f'every {record.time} days',
               }

    return messages.list_records_message.format(title=record.title,
                                                date=periods[record.type],
                                                id=record.id)

output:

record = Record(user=1234, title='month', date='month', time='25', type='month', need_delete=0, id=1)
record type = month
record date = month
record time = 25

Class Record:

class Record(NamedTuple):
    user: int
    title: str
    date: Union[type(datetime.datetime), str]
    time: Union[type(datetime.datetime), str]
    type: Literal['everyday', 'few_times_a_day', 'every_few_days',
                  'week', 'month', 'year', 'once']
    need_delete: bool
    id: Optional[int]

Error message:

File "C:Userspizhlo21DesktopFolderpythontg_bot_remindercontroller.py", line 121, in show_reminder
    await message.answer(make_record_text(item))
  File "C:Userspizhlo21DesktopFolderpythontg_bot_remindercontroller.py", line 146, in make_record_text
    'week': f'{days_of_week_dict[record.time]}',
KeyError: '25'

Why does it happen if I pass "month"?

2

Answers


  1. The issue is that you’re building the whole periods dict before knowing whether the record.time value is valid for that kind of period, and evaluating f'{days_of_week_dict[record.time]}' has Python try and access days_of_week_dict["25"], which isn’t going to fly.

    I switched your namedtuple for a dataclass below since it feels more accurate, but the actual fix is the match statement that only formats the human-readable period that’s appropriate for the type. (If your Python version is older than 3.10 and doesn’t have match, you can replace it with if self.type == "year", etc.)

    import dataclasses
    import datetime
    from typing import Union, Literal, Optional
    
    days_of_week_dict = {
        "monday": "every monday",
        "tuesday": "every tuesday",
        "wednesday": "every wednesday",
        "thursday": "every thursday",
        "friday": "every friday",
        "saturday": "every saturday",
        "sunday": "every sunday",
    }
    
    
    @dataclasses.dataclass()
    class Record:
        user: int
        title: str
        date: Union[datetime.datetime, str]
        time: Union[datetime.datetime, str]
        type: Literal[
            "everyday", "few_times_a_day", "every_few_days", "week", "month", "year", "once"
        ]
        need_delete: bool
        id: Optional[int]
    
        def format_period(self) -> str:
            match self.type:
                case 'year':
                    return f'every year {self.time}'
                case 'once':
                    return self.date
                case 'week':
                    return f'{days_of_week_dict[self.time]}'
                case 'month':
                    return f'every month on {self.time}'
                case 'several_min':
                    return f'every {self.time} minutes'
                case 'everyday':
                    return f'everyday at {self.time}'
                case 'several_hours':
                    return f'every {self.time} hours'
                case 'every_few_days':
                    return f'every {self.time} days'
                case _:
                    raise ValueError(f'Unknown record type: {self.type}')
    
    
    def make_record_text(record: Record) -> str:
        return f"{record=}; {record.format_period()}"
    
    
    print(
        make_record_text(
            Record(
                user=297850814,
                title="month",
                date="month",
                time="25",
                type="month",
                need_delete=False,
                id=1,
            )
        )
    )
    

    This outputs

    record=Record(user=297850814, title='month', date='month', time='25', type='month', need_delete=False, id=1);
    every month on 25
    
    Login or Signup to reply.
  2. The issue arises because:

    1. Call to days_of_week_dict will be evaluated when creating periods
    2. You used bracket notation with potentially non-existing dictionary keys

    You can fix that by changing bracket notation to .get() dictionary method, which will evaluate as None if the key is non-existing (or other value if you specify).

    So changing periods assignment to:

    periods = {'year': f'every year {record.time}',
               'once': record.date,
               'week': f'{days_of_week_dict.get(record.time)}',
               'month': f'every month on {record.time}',
               'several_min': f'every {record.time} minutes',
               'everyday': f'everyday at {record.time}',
               'several_hours': f'every {record.time} hours',
               'every_few_days': f'every {record.time} days',
               }
    

    Should fix the problem.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search