'Tis Data's Verification and Classes,

Skip to content

This doth be a machine-wrought text which may contain errors!

Data Validation and Classes

In Python (as is true of most other tongues), ‘tis possible to forge thine own objects, bearing their own values, rules, and functions. This practice is termed classes (in English tongue). We employ classes to gather kinred data and functions into a single unity, for instance an Ordre class which holds such data as ordre_id, kunde_navn, produkter, and hath functions like legg_til_produkt(), beregn_total(), etc.

The Book of Records (JSON) vs Classes (Objects)

JSON (the Object Nomenclature of JavaScript) ‘tis a manner to preserve and convey knowledge sans regard unto the tongue of craft, whereas a class be but a frame within one specific language alone.

When we would dispatch our wares o’er the web, or lodge them in parchment, oft shall we employ JSON (or tables within the vaults of databases).

When we labour with ordered matter in code, then it is classes wherewith we do deal.

In this module shall we observe how classes may be employed to validate data.

Veedio: Howe Classes Do Gather Data and Rules

The veeedio doth expound upon the nature of a class, and how classes may be employed to order and verify thy data.

‘Tis most easy to employ @dataclass from the dataclasses library. This granteth us liberty from penning much superfluous code to forge a class. (Such as the inherent __init__ and __repr__ functions [representation]).

An example of a class without the use of the dataclass decorator:

class Car:
    def __init__(self, make: str, model: str, year: int):
        self.make = make
        self.model = model
        self.year = year

    def __repr__(self):
        return f"{self.year} {self.make} {self.model}"

my_car = Car("Toyota", "Corolla", 2020)
print(my_car)  # The fruit of thine endeavor: 2020 Toyota Corolla

The British serpent-folk doth exclaim 'tis but a constructor, __init__?

An exemplar with the data-class ornament, achieving the same end as aforestated (yet with lesser code):

from dataclasses import dataclass

@dataclass
class Car:
    make: str
    model: str
    year: int

my_car = Car("Toyota", "Corolla", 2020)
print(my_car)  # The fruit of this labour is thus: Car(make='Toyota', model='Corolla', year=2020)

Easy Task I - Forge a Class

Do construct a class, named Person. This class shall possess these attributes:

  • name: The name of the person
  • eye_color: The hue of the person’s eyes
  • phone_number: The number by which one may summon the person via telephone
  • email: The address whereto one may send electronic missives unto the person

Instantiate (bring forth into being) an object of this Person class with valid values for each attribute, as shown in the example below.

@dataclass
class Person:
    ... # Thy code herein

bob_kaare = Person(name="Bob Kåre",
                   eye_color="blue",
                   phone_number="12345678",
                   email="bob_kaare@example.com")
print(bob_kaare)
Solution: A Dataclass unto the Man of Persons

Lo, here be one manner of remedy:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: str
    email: str

Medium Quest Two - Verification within the Class

In the exemplar aforementioned, we have not affixed any validation thereof. This signifyeth that one may fashion a Person with values most unseemly, such as:

@dataclass
class Person:
    ... # Thy code doth belong here

invalid_person = Person(name="",
                        eye_color="yes",
                        phone_number="12345",
                        email="not-an-email")
print(invalid_person)
# The fruit of this deed is thus: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')

‘Tis a matter fraught with peril, ‘twould surely breed technical debt in days to come. Yet, lo! there be simple means wherewithal one may bestow validation upon classes.

We shall commence our quest by gazing upon the verification of epistles. There do exist libraries wrought into Python itself which might aid us therein; yet, as we do this but to learn, we shall forge our own rudimentary validation, creating anew a class named “Email,” and examine the function denominated __post_init__ (solely for that which is termed a dataclass).

Marketh

We may also perform this deed within the very class of Person, yet it is oft better to forge distinct classes for things that may be re-used in other endeavors.

An Example upon an _postinit Function
from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        print(f"Verifying the epistle: {self.address}")
        # Thy code here

For a simple verification of an email address, one may check that it containeth both the symbol “@” and “.” among its characters. Or perchance thou mightest verify if the email doth match unto a regular expression pattern. (A more complex task, yet I pray thee seek knowledge upon the World Wide Web with alacrity!)

Solution: Code for simple email validation

Behold a likely remedy; we employ exceptions to “crash” should the epistle be invalid, this shall halt the program forthwith and grant an error message.

from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        if "@" not in self.address:
            raise ValueError(f"@ doth lack within the mail's address: {self.address}")
        if "." not in self.address.split("@")[1]:
            raise ValueError(f". doth lack within the domain of the mail's address: {self.address}")
        if " " in self.address:
            raise ValueError(f"The mail's address may contain no spaces: {self.address}")

# Test the code 
test = Email("hei@example.com")  # Valid
try:
    test = Email("heiexample.com")
except ValueError as e:
    print(e) # Invalid, lacking @

Medium Questio the Third - The Vouchsafing of Telephone Numerals

Fashion thou a class akin to that which was wrought for post-electronic missives, yet this time ‘tis for telephone numerals.

A Peril in Telephone Verification!

Canst thou mend th’ verification of telephone numbers to accept both letters (strings) and digits (numbers)? For instance, 12345678 and "12345678" shall both be deemed valid.

Doth also endeavour to append country codes as an attribute (a subordinate value unto the class). For example, 47 for Norway, 46 for Sweden

Medium Task IV - To Employ Verification within the Class of Persons

Now that we have forged verification for email and telephone number, ‘tis meet to employ these within the class of persons.

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: PhoneNumber  # Employ ye the PhoneNumber class
    email: Email               # Employ ye the Email class

A new trial doth arise!

Now that we have altered the Person class to employ PhoneNumber and Email classes, ‘tis needful also for us to change how we instantiate (create) a Person. We must now first create a PhoneNumber and an Email object, ere we can fashion a Person.

bob_kaare = Person(name="Bob Kåre",
                   eye_color="blue",
                   phone_number=PhoneNumber("12345678"),  # Behold the alteration here
                   email=Email("bob_kaare@example.com"))  # Behold the alteration here
print(bob_kaare)

# Beware: a modification is requisite in the manner whereby we extract these values also
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number)  # .country_code(?)

Hard Task V - Properties within Classes (Optional)

When we employ objects to signify such values as post and telephone number, ‘tis needful that we specify the sub-value (for example, address for post and number for telephone number) each time we seek to extract said value. This doth prove somewhat tedious in length. Yet fortune favors us with a remedy, through the use of the @property decorator within a class, whereby one may retrieve the value directly from the object, without being bound to name the sub-value.

This, however, presents yet another trial, which is that we require the __init__ function within the Person class. For it befits not one to use the same name for both a property and an attribute within a dataclass.

from dataclasses import dataclass

@dataclass
class EksempelVerdi:
    attributt: str

@dataclass
class Person:
    name: str
    _verdi: EksempelVerdi  # A hidden variable (doth commence with an underscore to shew it is "private")

    def __init__(self, name: str, verdi: EksempelVerdi):
        self.name = name
        self._verdi = verdi

    @property
    def verdi(self):
        return self._verdi.attributt  # Fetcheth the inner value most directly

# To try thine code
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi)  # The output shall be: Noe tekst

Mark

Properties are most peculiar in that they require no parameters, nor parentheses to be set in motion. In this instance, we retrieve person.value without any brackets (not person.value()) albeit it be technically a function.

An other example which doth accept both str and EksempelVerdi
@dataclass
class Person:
    name: str
    _verdi: str

    def __init__(self, name: str, verdi: str | EksempelVerdi):
        self.name = name
        if isinstance(verdi, EksempelVerdi):
            self._verdi = verdi
        elif isinstance(verdi, str):
            self._verdi = EksempelVerdi(verdi)
        else:
            raise TypeError("`verdi` must be of the sort `str` or `EksempelVerdi`")

    @property
    def verdi(self) -> str:
        return self._verdi.attributt  # Fetcheth out the sub-value direct

# Try the code
person = Person(name="Alice", verdi="Some text")
print(person.verdi)  # Yielding: Some text

Hard Task the Sixth - Proprietals with Logic (Optional)

Amend thee Person, adding thereto a new attribute named birthday (day of nativity). This shall be of type datetime.date (from the datetime library).

Thereafter, thou shalt forge these proprietals:

  • Forge a proprial age that doth compute the person’s years based upon birthday and this day’s date.
  • Forge a proprial is_adult which yieldeth True if the person is eighteen years or more in age, else False.
Resolution: Age and adulthood as properties

Here be a likely solution:

from dataclasses import dataclass
from datetime import date

@dataclass
class Person:
    name: str
    birthday: date

    @property
    def age(self) -> int:
        """Doth compute the years lived based upon birth-date and this day's date"
        today = date.today()
        age = today.year - self.birthday.year
        # Adjust downward by one if hath not yet had their birthday in this year
        if (today.month, today.day) < (self.birthday.month, self.birthday.day):
            age -= 1
        return age

    @property
    def is_adult(self) -> bool:
        """Returneth True if the person be of eighteen years or more"
        return self.age >= 18

# Do test the code
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age)       # E.g. 18 if this day passeth the fifteenth of May, two thousand twenty-three
print(person.is_adult)  # True

Yet another trial befalls thee!

Canst thou contrive it so that the instantiation of Person shall accept both datetime.date and a textual inscription in the fashion of "DD-MM-YYYY" for thy birthday? (A whisper to thine ear: employ datetime.strptime to transmute the string into a datetime.date)