13. The generic classes [BaseEntity] and [MyException]
We will now define two classes that we will use regularly going forward.

13.1. The MyException class
The [MyException] class (MyException.py) provides a custom exception class:
# a custom exception class deriving from [BaseException]
class MyException(BaseException):
# constructor
def __init__(self: object, code: int, message: str):
# parent
BaseException.__init__(self, message)
# error code
self.code = code
# toString
def __str__(self):
return f"MyException[{self.code}, {super().__str__()}]"
# getter
@property
def code(self) -> int:
return self.__code
# setter
@code.setter
def code(self, code: int):
# The error code must be a positive integer
if isinstance(code, int) and code > 0:
self.__code = code
else:
# exception
raise BaseException(f"Invalid error code {code}")
Notes
- line 2: the [MyException] class derives from the predefined [BaseException] class;
- line 4: the constructor accepts two parameters:
- [code]: an integer error code;
- [message]: an error message;
- line 6: the error message is passed to the parent class;
- lines 14–27: the [code] attribute is accessed via a getter/setter;
- lines 23–24: the validity of the [code] attribute is checked: it must be an integer greater than 0;
13.2. The [BaseEntity] class
The [BaseEntity] class will be the parent class of most of the classes we create to encapsulate information about an object. Moving forward, we will primarily use two types of classes:
- classes whose sole purpose is to encapsulate information about a single object in one place. These will have no behaviors (methods) other than getters/setters and a display function (__str__). If there are N objects to manage, these classes are instantiated N times. [BaseEntity] will be the parent class of this type of class;
- classes whose primary role is to encapsulate methods and very little information. These classes will be instantiated only once (singleton). Their role is to implement an application’s algorithms;
The [BaseEntity] class is as follows:
# imports
import json
import re
from MyException import MyException
class BaseEntity(object):
# Properties excluded from the class state
excluded_keys = []
# class properties
@staticmethod
def get_allowed_keys() -> list:
# id: object identifier
return ["id"]
# toString
def __str__(self) -> str:
return self.asjson()
# getter
@property
def id(self) -> int:
return self.__id
# setter
@id.setter
def id(self, id):
# the id must be an integer >= 0
try:
id = int(id)
error = id < 0
except:
error = True
# error?
if error:
raise MyException(1, f"The ID of an entity {self.__class__} must be an integer >= 0")
else:
self.__id = id
def fromdict(self, state: dict, silent=False):
…
def set_value(self, key: str, value, new_attributes) -> dict:
…
def asdict(self, included_keys: list = None, excluded_keys: list = []) -> dict:
…
def asjson(self, excluded_keys: list = []) -> str:
…
def fromjson(self, json_state: str):
…
Comments
- The purpose of the [BaseEntity] class is to facilitate Object/Dictionary and Object/JSON conversions. It provides the following methods:
- [asdict]: returns a dictionary of the object’s properties;
- [fromdict]: creates an object from a dictionary;
- [asjson]: returns the object’s JSON string, similar to the [__str__] function;
- [fromjson]: constructs an object from its JSON string;
- The [BaseEntity] class is intended to be derived from and not used as-is;
- lines 22–25: the [BaseEntity] class has only one property, the integer [id]. This property is the object’s identifier. In practice, it is often useful to be able to distinguish between instances of the same class. We will do this using this property, which is unique to each instance. Furthermore, objects often come from databases where they are identified by a primary key, typically an integer. In such cases, [id] will serve as the primary key;
- lines 27–40: the setter for the [id] property. We verify that it is an integer >= 0. If this is not the case, an exception of type [MyException] is thrown (line 39);
- line 10: [excluded_keys] is a class attribute, not an instance attribute. Therefore, we write [BaseEntity.excluded_keys]. This class attribute is a list containing the class properties that do not participate in Object/Dictionary and Object/JSON conversions;
- lines 12–16: [get_allowed_keys] returns the list of the class’s properties. In a Dictionary → Object or JSON → Object conversion, only keys present in this list will be accepted. Each class deriving from the [BaseEntity] class will need to redefine this list;
It is important to understand here that the properties and functions of the [BaseEntity] class are accessible to classes derived from [BaseEntity]. This is the key point to grasp.
We will now examine the code of the [BaseEntity] class in detail. It is quite advanced. Beginner readers may simply read the description of each function’s role without delving into the code itself.
13.2.1. The [BaseEntity.fromdict] method
13.2.1.1. Definition
The [fromdict] method allows you to initialize a [BaseEntity] object or a derived object from a dictionary:
def fromdict(self, state: dict, silent=False):
# update the object
# allowed keys
allowed_keys = self.__class__.get_allowed_keys()
# iterates over the keys in state
for key, value in state.items():
# Is the key allowed?
if key not in allowed_keys:
if not silent:
raise MyException(2, f"The key {key} is not allowed")
else:
# we try to assign the value to the key
# let any exceptions be raised
setattr(self, key, value)
# return the object
return self
Comments
- line 1: the function receives the [state] dictionary as a parameter, from which the current object will be initialized;
- line 4: we call the static function [get_allowed_keys] of the class that called the [fromdict] function. If we are dealing with a class derived from [BaseEntity] and that derived class has redefined the static function [get_allowed_keys], then it is the [get_allowed_keys] function that is called. Each derived class redefines this static function to declare its properties;
- line 6: we iterate through the keys and values of the [state] dictionary;
- line 8: if the key [key] is not one of the class’s properties, then either:
- it is ignored;
- an exception is thrown (line 10). The developer specifies their preference by passing the appropriate [silent] parameter (line 1). The default value of [silent] causes an exception to be thrown if an attempt is made to initialize the object with a property it does not have;
- line 14: if the key is among the object’s properties, then it is assigned to the object [self] using the predefined function [setattr];
- line 16: the function returns the initialized object;
13.2.1.2. Examples

13.2.1.2.1. The [Utils] class
The [Utils] class (Utils.py) is as follows:
class Utils:
# static method
@staticmethod
def is_string_ok(string: str) -> bool:
# Is string a string?
error = not isinstance(string, str)
if not error:
# Is the string empty?
error = string.strip() == ''
# result
return not error
It defines, on lines 3–11, a static method that returns a Boolean true if its parameter [str] is a non-empty string;
13.2.1.2.2. The [Person] class
The [Person] class (Person.py) derives from the [BaseEntity] class:
# imports
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
# Person class
class Person(BaseEntity):
# Properties excluded from the class state
excluded_keys = []
# class properties
# id: person's identifier
# first_name: person's first name
# last_name: person's last name
# age: person's age
@staticmethod
def get_allowed_keys() -> list:
# id: object ID
return BaseEntity.get_allowed_keys() + ["last_name", "first_name", "age"]
# getters
@property
def first_name(self) -> str:
return self.__first_name
@property
def last_name(self) -> str:
return self.__last_name
@property
def age(self) -> int:
return self.__age
# setters
@first_name.setter
def first_name(self, first_name: str):
# the first_name must not be empty
if Utils.is_string_ok(first_name):
self.__first_name = first_name.strip()
else:
raise MyException(11, "The first name must be a non-empty string")
@last_name.setter
def name(self, name: str):
# the first name must not be empty
if Utils.is_string_ok(name):
self.__name = name.strip()
else:
raise MyException(12, "The name must be a non-empty string")
@age.setter
def age(self, age: int):
# age must be an integer >= 0
error = False
if isinstance(age, int):
if age >= 0:
self.__age = age
else:
error = True
else:
error = True
# error?
if error:
raise MyException(13, "Age must be an integer >= 0")
- line 8: the [Person] class derives from the [BaseEntity] class;
- lines 8–65: we have retained most of the [Person] class encountered earlier. The differences are as follows:
- the class no longer has a constructor;
- the class uses the [MyException] exception, e.g., line 65;
- it has a static method, [get_allowed_keys], lines 17–20, which defines the list of its properties. The properties specific to the [Person] class are added to those of the parent class [BaseEntity];
- it has a static list [excluded_keys], which we will return to later;
13.2.1.2.3. The [Teacher] class
The [Teacher] class (Teacher.py) derives from the [Person] class:
# imports
from MyException import MyException
from Person import Person
from Utils import Utils
# Teacher class
class Teacher(Person):
# Properties excluded from the class state
excluded_keys = []
# class properties
# id: person's identifier
# first_name: person's first name
# last_name: person's last name
# age: person's age
# subject: subject taught
@staticmethod
def get_allowed_keys() -> list:
# id: object ID
return Person.get_allowed_keys() + ["subject"]
# properties
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# the discipline must be a non-empty string
if Utils.is_string_ok(discipline):
self.__discipline = discipline
else:
raise MyException(21, "The discipline must be a non-empty string")
# show method
def show(self):
print(f"Teacher[{self.id}, {self.first_name}, {self.last_name}, {self.age}]")
- line 8: the [Teacher] class extends (or derives from) the [Person] class;
- lines 18–21: define the list of properties for the class;
- lines 37–38: the [show] method displays the teacher’s identity;
13.2.1.2.4. The [config] configuration
The example scripts use the following [config] configuration:
def configure():
import os
# configuration file directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Absolute paths of the directories to include in the syspath
absolute_dependencies = [
# the BaseEntity class
f"{script_dir}/entities",
]
# Update the syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# return the configuration
return {}
- lines 8–10: the directories containing the project’s dependencies;
- lines 14–15: the Python Path is constructed;
- line 18: returns an empty dictionary (there are no other configurations besides the syspath);
13.2.1.2.5. The [fromdict_01] script
The [fromdict_01] script is as follows:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56})
teacher1.show()
- Line 10: We create a [Teacher] object from a dictionary. To do this, we use the class’s default constructor to create a [Teacher] object to which we apply the [fromdict] method. It’s important to understand that here, the [fromdict] method being executed is that of the parent class [BaseEntity]. In fact:
- the [fromdict] method is first looked for in the [Teacher] class. It does not exist;
- it is then looked for in the parent class [Person]. It does not exist;
- it is then looked for in the parent class [BaseEntity]. It exists;
- line 11: the [Teacher] object is displayed;
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_01.py
Teacher[1, paul, lourou, 56]
Process finished with exit code 0
13.2.1.2.6. The script [fromdict_02]
The script [fromdict_02] is as follows:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "", "age": 56})
teacher1.show()
- Line 10: We create a teacher with an empty first name. This should raise an exception because the [Person] class does not accept empty first names. This example illustrates the difference between a dictionary and an object. The latter can validate its properties, whereas the dictionary cannot;
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py", line 10, in <module>
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "", "age": 56})
File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 55, in fromdict
setattr(self, key, value)
File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\Personne.py", line 42, in first_name
raise MyException(11, "The first name must be a non-empty string")
MyException.MyException: MyException[11, The first name must be a non-empty string]
Process finished with exit code 1
13.2.1.2.7. The script [fromdict_03]
The script [fromdict_03] is as follows:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "albert", "age": 56, "gender": "M"})
teacher1.show()
- Line 10: We create a Teacher from a dictionary containing a key (gender) that does not belong to the [Teacher] class. An exception should then be raised;
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py
Traceback (most recent call last):
File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py", line 10, in <module>
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "albert", "age": 56, "gender": "M"})
File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 51, in fromdict
raise MyException(2, f"The key [{key}] is not allowed")
MyException.MyException: MyException[2, the key [sex] is not allowed]
Process finished with exit code 1
13.2.1.2.8. The script [fromdict_04]
The script [fromdict_04] is a copy of [fromdict_03] with one minor difference:
# configure the application
import config
config = config.configure()
# the syspath is configured - we can now import modules
from Teacher import Teacher
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "albert", "age": 56, "gender": "M"}, silent=True)
teacher1.show()
- line 10: we used the [silent=True] parameter to indicate that if a dictionary key is not a property of the [Teacher] class, it should simply be ignored. In this case, no exception will be raised;
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_04.py
Teacher[1, albert, lourou, 56]
Process finished with exit code 0
13.2.2. The [BaseEntity.asdict] method
13.2.2.1. Definition
The [BaseEntity.asdict] method returns a dictionary whose keys are the object’s properties:
def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
# object attributes
attributes = self.__dict__
# new attributes
new_attributes = {}
# iterate over the attributes
for key, value in attributes.items():
# if the key is explicitly requested
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# otherwise, if the key is not excluded
elif not included_keys and key not in self.__class__.excluded_keys and key not in excluded_keys:
self.set_value(key, value, new_attributes)
# we return the attributes dictionary
return new_attributes
Comments
- line 1: the [asdict] function returns the dictionary of the object's properties;
- line 1: [included_keys]: the list of keys to include in the dictionary;
- line 1: [excluded_keys]: the list of keys to exclude from the dictionary;
- line 3: the property [self.__dict__] returns the object’s property dictionary. The property names are the keys and their values are the dictionary’s values. An object may contain references to other objects. In that case, the property names are prefixed with the name of the class to which they belong. This is something we do not want. We want the properties without their prefix;
- line 3: it is important to understand here that if the [asdict] function is executed within a class derived from [BaseEntity], the [self.__dict__] property returns the dictionary of properties for the derived object;
- line 5: the dictionary we are going to construct;
- line 7: we iterate over the values of [self.__dict__] in the form (key, value);
- line 9: if the current key belongs to the list of keys to include, then it is added to the [new_attributes] dictionary using the [set_value] function, which we will describe shortly;
- line 12: if the [included_keys] parameter is not present, then the [excluded_keys] parameter is used. If the property is not among the properties to be excluded, then it is added to the [new_attributes] dictionary;
- line 12: there are several ways to exclude a property from the dictionary:
- it has been defined at the class attribute level [excluded_keys];
- it has been defined in the [excluded_keys] list passed to the [asdict] function;
- the [included_keys] parameter is present and does not include the property;
- line 15: we return the dictionary [new_attributes]
The [set_value] function in lines 10 and 13 is as follows:
@staticmethod
def set_value(key: str, value, new_attributes: dict):
# keys can be of the form __Class__key
match = re.match("^.*?__(.*?)$", key)
if match:
# note the new key
newkey = match.groups()[0]
else:
# the key remains unchanged
newkey = key
# insert the new key into the dictionary [new_attributes]
# converting the associated value to one of the types
# dict, list, or simple type
new_attributes[newkey] = BaseEntity.check_value(value)
Comments
- line 4: check if the key is in the form __Class_key. This is the form it takes if it belongs to an object included in the main object. In this case, we only want to keep the string [key];
- line 7: we keep only the string following the last two underscored characters of the string;
- lines 8–10: if the key is not in the form __Class_key, we keep it as is;
- lines 11–14: the value associated with the key [newkey] is calculated by the static method [BaseEntity.check_value];
The static method [BaseEntity.check_value] is as follows:
@staticmethod
def check_value(value):
# the value can be of type BaseEntity, list, dict, or a simple type
# Is value an instance of BaseEntity?
if isinstance(value, BaseEntity):
value2 = value.asdict()
# Is value a list?
elif isinstance(value, list):
value2 = BaseEntity.list2list(value)
# Is value a dictionary?
elif isinstance(value, dict):
value2 = BaseEntity.dict2dict(value)
# value is a simple type
else:
value2 = value
# return the result
return value2
- line 1: the [check_value] method is static (a class method, not an instance method). It takes as a parameter the value to be associated with a dictionary key:
- line 17: if this value is a simple type, it remains unchanged;
- lines 5-6: if this value is of type BaseEntity, the value is replaced by its dictionary. This results in a recursive call;
- lines 8–9: if this value is a list, then it is replaced by the value [BaseEntity.list2list];
- lines 11–12: if this value is a dictionary, then it is replaced by the value [BaseEntity.dict2dict];
The static method [BaseEntity.list2list] is as follows:
@staticmethod
def list2list(list: list) -> list:
# inspect the elements of the list
newlist = []
for value in list:
newlist.append(BaseEntity.check_value(value))
# return the new list
return newlist
- line 2: the method receives a list and returns a list;
- lines 5-6: each value in the list passed as a parameter is replaced with the value returned by the static method [BaseEntity.check_value]. This is therefore a recursive call. The static method [BaseEntity.check_value] is called until its parameter [value] is a simple type (not a BaseEntity, list, or dict type);
The static method [BaseEntity.dict2dict] is as follows:
@staticmethod
def dict2dict(dictionary: dict) -> dict:
# inspect the dictionary's elements
newdict = {}
for key, value in dictionary.items():
newdict[key] = BaseEntity.check_value(value)
# return the new dictionary
return newdict
- line 2: the method receives a dictionary and returns a dictionary;
- lines 5-6: each value in the dictionary passed as a parameter is replaced with the value returned by the static method [BaseEntity.check_value]. This is therefore a recursive call. The static method [BaseEntity.check_value] is called until its parameter [value] is a simple type (not a BaseEntity, list, or dict);
13.2.2.2. Examples
The script [asdict_01] demonstrates various uses of the [asdict] method:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
from BaseEntity import BaseEntity
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56})
dict1 = teacher1.asdict()
print(type(dict1))
print(teacher1.__dict__)
print(dict1)
print(teacher1.asdict(excluded_keys=["_Person__age"]))
Teacher.excluded_keys = ["_Person__first_name"]
print(teacher1)
# another teacher
teacher2 = Teacher().fromdict({"id": 2, "last_name": "abélard", "first_name": "béatrice", "age": 57})
print(teacher2.asdict())
print(teacher2.asdict(included_keys=["_Person__last_name"]))
# a list of entities within an entity
Teacher.excluded_keys = []
entity1 = BaseEntity()
teachers = [teacher1, teacher2]
setattr(entity1, "teachers", teachers)
print(entity1.asdict())
# a dictionary of entities within an entity
subjects = {"math": teacher1, "english": teacher2}
setattr(entity1, "subjects", subjects)
print(entity1.asdict())
The results of the execution are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asdict_01.py
<class 'dict'>
{'_BaseEntity__id': 1, '_Personne__nom': 'lourou', '_Personne__prénom': 'paul', '_Personne__âge': 56}
{'id': 1, 'lastName': 'lourou', 'firstName': 'paul', 'age': 56}
{'id': 1, 'last_name': 'lourou', 'first_name': 'paul'}
{"id": 1, "lastName": "lourou", "age": 56}
{'id': 2, 'last_name': 'abélard', 'age': 57}
{'last_name': 'abélard'}
{'teachers': [{'id': 1, 'last_name': 'lourou', 'first_name': 'paul', 'age': 56}, {'id': 2, 'last_name': 'abélard', 'first_name': 'béatrice', 'age': 57}]}
{'teachers': [{'id': 1, 'last_name': 'lourou', 'first_name': 'paul', 'age': 56}, {'id': 2, 'last_name': 'abélard', 'first_name': 'béatrice', 'age': 57}], 'subjects': {'math': {'id': 1, 'last_name': 'lourou', 'first_name': 'paul', 'age': 56}, 'french': {'id': 2, 'last_name': 'abélard', 'first_name': 'béatrice', 'age': 57}}}
Process finished with exit code 0
- Line 4 demonstrates the advantage of the [asdict] method over using the [__dict__] property. The properties are stripped of their class prefix. This makes them easier to display;
- There are several ways to use the [asdict] method:
- if you want all properties: use the [asdict] method without parameters;
- if you only want certain properties:
- there are more properties to include than to exclude: use the single [excluded_keys] parameter;
- There are fewer properties to include than to exclude: we will use only the [included_keys] parameter;
13.2.3. The [BaseEntity.asjson] method
This method returns the JSON string of a [BaseEntity] object or its derived classes. It outputs the JSON string of the dictionary returned by the [asdict] method. Its code is as follows:
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
# the JSON string
return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
- line 1: the parameters of the [asjson] method are those of the [asdict] method;
Here is an example (asjson_01) using this method:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
from BaseEntity import BaseEntity
# a teacher
teacher1 = Teacher().fromdict({"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56})
print(type(teacher1.asjson()))
print(teacher1.asjson(excluded_keys=["_Person__age"]))
Teacher.excluded_keys = ["_Person__first_name"]
print(teacher1.asjson())
# another teacher
teacher2 = Teacher().fromdict({"id": 2, "last_name": "abélard", "first_name": "béatrice", "age": 57})
print(teacher2.asjson())
print(teacher2.asjson(included_keys=["_Person__last_name"]))
# a list of entities within an entity
Teacher.excluded_keys = []
entity1 = BaseEntity()
teachers = [teacher1, teacher2]
setattr(entity1, "teachers", teachers)
print(entity1.asjson())
# A dictionary of entities within an entity
subjects = {"math": teacher1, "english": teacher2}
setattr(entity1, "subjects", subjects)
print(entity1.asjson())
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asjson_01.py
<class 'str'>
{"id": 1, "last_name": "lourou", "first_name": "paul"}
{"id": 1, "last_name": "lourou", "age": 56}
{"id": 2, "last_name": "abélard", "age": 57}
{"last_name": "abélard"}
{"teachers": [{"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56}, {"id": 2, "last_name": "abélard", "first_name": "béatrice", "age": 57}]}
{"teachers": [{"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56}, {"id": 2, "last_name": "abélard", "first_name": "béatrice", "age": 57}], "subjects": {"math": {"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56}, "french": {"id": 2, "last_name": "abélard", "first_name": "béatrice", "age": 57}}}
Process finished with exit code 0
The [BaseEntity.__str__] method uses the [asjson] method to display the identity of the [BaseEntity] object or its derived objects:
# toString
def __str__(self) -> str:
return self.asjson()
13.2.4. The [BaseEntity.fromjson] method
The [BaseEntity.fromjson] method allows you to initialize an object of type [BaseEntity] or a derived type from a JSON dictionary. Its code is as follows:
def fromjson(self, json_state: str, silent: bool = False):
# Update the object's state from the JSON string
return self.fromdict(json.loads(json_state), silent=silent)
- Line 1: The method takes two parameters:
- [json_state]: the JSON dictionary used to initialize the [BaseEntity] object;
- [silent]: to indicate whether the presence in the JSON dictionary of a key that cannot be accepted as a property of the [BaseEntity] object triggers an exception (silent=False) or is simply ignored (silent=True);
- Line 3: We start by constructing the Python dictionary representing the JSON dictionary, then use the [fromdict] method to initialize the [BaseEntity] object from this Python dictionary;
Here is an example (fromjson_01):
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from Teacher import Teacher
import json
# a teacher
json1 = json.dumps({"id": 1, "last_name": "lourou", "first_name": "paul", "age": 56})
teacher1 = Teacher().fromjson(json1)
teacher1.show()
- line 11: we create the JSON string of a dictionary;
- line 12: an [Enseignant] object is initialized with this string;
- line 13: the teacher is displayed;
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromjson_01.py
Teacher[1, paul, lourou, 56]
Process finished with exit code 0
13.2.5. The [main] script
The [main] script summarizes the various methods encountered:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import
from BaseEntity import BaseEntity
from MyException import MyException
# a class
class ChildEntity(BaseEntity):
# attributes excluded from the class state
excluded_keys = []
@staticmethod
def get_allowed_keys():
return ["att1", "att2", "att3", "att4"]
@property
def att1(self) -> int:
return self.__att1
@att1.setter
def att1(self, value: int):
if 10 >= value >= 1:
self.__att1 = value
else:
raise MyException(1, f"The [att1] attribute expects a value in the range [1,10] ({value})")
# ChildEntity configuration
ChildEntity.excluded_keys = []
# ChildEntity instance
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# Be careful with property names
# these are the names used in [excluded_keys] and [included_keys]
print(child.__dict__)
# properties not prefixed by their class
print(child)
# ChildEntity instance
try:
child = ChildEntity().fromdict({"att1": 1, "att5": 5})
print(child)
except MyException as error:
print(error)
# ChildEntity instance
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)
# Excluding certain keys from the instance state
ChildEntity.excluded_keys = ['att3']
print(child)
# Explicitly exclude a key from the display
# it is added to those excluded globally at the class level
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))
# advantage of the class over the dictionary
# It can verify the validity of its contents
try:
child = ChildEntity().fromdict({"att1": 20})
except MyException as error:
print(error)
# ChildEntity instance
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# ChildEntity instance containing another ChildEntity instance
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)
# included_keys take precedence over excluded_keys, which are then ignored
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))
The results of the execution are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/main.py
{'_ChildEntity__att1': 1, 'att2': 2}
{"att1": 1, "att2": 2}
MyException[2, key [att5] is not allowed]
{"att1": 1, "att2": 2, "att3": 3, "att4": 4}
{"att1": 1, "att2": 2, "att4": 4}
{'att2': 2, 'att4': 4}
{"att1": 1, "att4": 4}
MyException[1, The attribute [att1] expects a value in the range [1,10] (20)]
{"att1": 10, "att2": 20, "att4": {"att1": 1, "att2": 2, "att4": 4}}
{'att1': 1, 'att3': 3}
Process finished with exit code 0
Note line 2 of the results: it is the property [ChildEntity.__dict__] (line 38 of the code) that allows us to determine the names of the properties to include in the [included_keys] and [excluded_keys] lists. Note, still on line 2 of the results, that depending on whether the property is defined within the class via a getter/setter or created as one would create a dictionary key, it may or may not be prefixed with the class name [ChildEntity].