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
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)
Task I - Forge a Class
Do construct a class, named Person. This class shall possess these attributes:
name: The name of the personeye_color: The hue of the person’s eyesphone_number: The number by which one may summon the person via telephoneemail: 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
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 @
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
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(?)
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
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
agethat doth compute the person’s years based uponbirthdayand this day’s date. - Forge a proprial
is_adultwhich yieldethTrueif the person is eighteen years or more in age, elseFalse.
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)
