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:
# une classe d'exception propriétaire dérivant de [BaseException]
class MyException(BaseException):
# constructeur
def __init__(self: object, code: int, message: str):
# parent
BaseException.__init__(self, message)
# code erreur
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):
# le code d'erreur doit être un entier positif
if isinstance(code, int) and code > 0:
self.__code = code
else:
# exception
raise BaseException(f"code erreur {code} incorrect")
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:
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:
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:
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:
- 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:
- 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:
- 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:
- 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
Enseignant[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:
- 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>
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 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 prénom
raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")
MyException.MyException: MyException[11, Le prénom doit être une chaîne de caractères non vide]
Process finished with exit code 1
13.2.1.2.7. The script [fromdict_03]
The script [fromdict_03] is as follows:
- 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>
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "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"la clé [{key}] n'est pas autorisée")
MyException.MyException: MyException[2, la clé [sexe] n'est pas autorisée]
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:
- 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
Enseignant[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:
# attributs de l'objet
attributes = self.__dict__
# les nouveaux attributs
new_attributes = {}
# on parcourt les attributs
for key, value in attributes.items():
# si la clé est explicitement demandée
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# sinon, si la clé n'est pas exclue
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)
# on rend le dictionnaire des attributs
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:
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:
- 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:
- 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:
- 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:
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, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}
{'id': 1, 'nom': 'lourou', 'prénom': 'paul'}
{"id": 1, "nom": "lourou", "âge": 56}
{'id': 2, 'nom': 'abélard', 'âge': 57}
{'nom': 'abélard'}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}]}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}], 'matières': {'maths': {'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, 'français': {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 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:
- line 1: the parameters of the [asjson] method are those of the [asdict] method;
Here is an example (asjson_01) using this method:
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, "nom": "lourou", "prénom": "paul"}
{"id": 1, "nom": "lourou", "âge": 56}
{"id": 2, "nom": "abélard", "âge": 57}
{"nom": "abélard"}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}]}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}], "matières": {"maths": {"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, "français": {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 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:
- 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):
- 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
Enseignant[1, paul, lourou, 56]
Process finished with exit code 0
13.2.5. The [main] script
The [main] script summarizes the various methods encountered:
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, la clé [att5] n'est pas autorisée]
{"att1": 1, "att2": 2, "att3": 3, "att4": 4}
{"att1": 1, "att2": 2, "att4": 4}
{'att2': 2, 'att4': 4}
{"att1": 1, "att4": 4}
MyException[1, L'attribut [att1] attend une valeur dans l'intervalle [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].