# person_step06.py
#
# ICS 33 Spring 2026
# Code Example
#
# This version establishes the immutability of the name and birthdate
# attributes, by taking control of what happens when the attributes are
# either set or deleted.


class Person:
    _fields = ('name', 'birthdate')


    def __init__(self, name, birthdate):
        self._values = (name, birthdate)


    def age(self, as_of_date):
        if self.birthdate > as_of_date:
            raise ValueError(f'Person was not born yet on {as_of_date}')

        years_old = as_of_date.year - self.birthdate.year

        if (self.birthdate.month, self.birthdate.day) >= (as_of_date.month, as_of_date.day):
            years_old -= 1

        return years_old


    def __eq__(self, other):
        return isinstance(other, Person) and self._values == other._values


    def __hash__(self):
        return hash(self._values)


    def __getattr__(self, name):
        try:
            return self._values[self._fields.index(name)]
        except ValueError:
            raise AttributeError(f"Person object has no attribute '{name}'")


    def __setattr__(self, name, value):
        # Here, we'll disallow the attributes corresponding to our fields from
        # having new values assigned to them.  Any other attributes (including
        # our _fields attribute internally) will work normally, by falling back
        # to the usual __setattr__() method provided by the object class.
        if name in self._fields:
            raise AttributeError(f"Attribute '{name}' of Person object cannot be assigned")
        else:
            super().__setattr__(name, value)


    def __delattr__(self, name):
        # We'll handle attribute deletion the same way, disallowing the deletion
        # of fields corresponding to our attributes.  Since those attributes can
        # never exist, deletion would have failed, anyway, but by overriding this,
        # we can raise an exception with an error message that will make more
        # sense (i.e., it'll say "you can't do that" instead of "that attribute
        # does not exist").
        if name in self._fields:
            raise AttributeError(f"Attribute '{name}' of Person object cannot be deleted")
        else:
            super().__delattr__(name)
