|
| 1 | +# Base classes |
| 2 | + |
| 3 | +Base classes are intended to provide data modeling, validation and configuration management by utilizing normal python classes. For example, given the following simple class |
| 4 | + |
| 5 | +```python |
| 6 | +class Person: |
| 7 | + name = fields.String(default="ahmed") |
| 8 | +``` |
| 9 | + |
| 10 | +The user should be able to create/store multiple instances of `Person` with valid data. |
| 11 | + |
| 12 | +To achieve this, we implemented base classes using `meta` classes and property descriptors. (a small note, after python `3.7` data classes seems a better option that can be used later). |
| 13 | + |
| 14 | +At first, to explain why we need to implement custom base class/model, we will illustrate the following examples: |
| 15 | + |
| 16 | +If we have the same class called `Person`, with the following definition: |
| 17 | + |
| 18 | + |
| 19 | +```python |
| 20 | +class Person: |
| 21 | + name = fields.String(default="ahmed") |
| 22 | +``` |
| 23 | + |
| 24 | +Accessing the class variable `name` from class or instance level will yield the same value, an instance of `String` field: |
| 25 | + |
| 26 | +```python |
| 27 | +Person.name #=> <jumpscale.core.base.fields.String object at 0x7efd89980c18> |
| 28 | + |
| 29 | +p = Person() |
| 30 | +p.name #=> <jumpscale.core.base.fields.String object at 0x7efd89980c18> |
| 31 | +``` |
| 32 | + |
| 33 | +The solution to this problem is using data descriptors (see https://docs.python.org/3/howto/descriptor.html), so the following class: |
| 34 | + |
| 35 | +```python |
| 36 | +class Person(Base): |
| 37 | + name = fields.String(default="ahmed") |
| 38 | +``` |
| 39 | + |
| 40 | +Should have different behavior when accessing `name` from a class or an objects, so, it will be converted by the meta class to a new class like: |
| 41 | + |
| 42 | +```python |
| 43 | +class Person(Base): |
| 44 | + def __init__(self): |
| 45 | + self.__name = "ahmed" |
| 46 | + |
| 47 | + @property |
| 48 | + def get_name(self): |
| 49 | + return self.__name |
| 50 | + |
| 51 | + @property |
| 52 | + def set_name(self, value): |
| 53 | + self.__name == value |
| 54 | + |
| 55 | + name = property(get_name, set_name) |
| 56 | +``` |
| 57 | + |
| 58 | +And accessing `name` from class and object levels will yield: |
| 59 | + |
| 60 | +```python |
| 61 | +Person.name #=> <property object at 0x7efd89a259f8> |
| 62 | + |
| 63 | + |
| 64 | +p = Person() |
| 65 | +p.name #=> "ahmed" |
| 66 | +``` |
| 67 | + |
| 68 | +Parent relationship is supported too, every instance can have a parent object (which must be a `Base` type too) |
| 69 | + |
| 70 | +## BaseMeta |
| 71 | + |
| 72 | +This [metaclass](https://docs.python.org/3/reference/datamodel.html#metaclasses) will convert normal classes to a new class with "injected" properties. |
| 73 | + |
| 74 | +Also, this `metaclass` adds all field information inside `_fields` class variable. |
| 75 | + |
| 76 | + |
| 77 | +```python |
| 78 | + def __new__(cls, name: str, based: tuple, attrs: dict) -> type: |
| 79 | + """ |
| 80 | + get a new class with all field attributes replaced by property data descriptors. |
| 81 | +
|
| 82 | + Args: |
| 83 | + name (str): class name |
| 84 | + based (tuple): super class types (classes) |
| 85 | + attrs (dict): current attributes |
| 86 | +
|
| 87 | + Returns: |
| 88 | + type: a new class |
| 89 | + """ |
| 90 | + # will collect class fields |
| 91 | + cls_fields = {} |
| 92 | + ... |
| 93 | + ... |
| 94 | +``` |
| 95 | + |
| 96 | +See complete implementation at [meta.py](https://github.com/threefoldtech/js-ng/blob/fa1582b83c36a8b18094fd208d04499a8d0f289d/jumpscale/core/base/meta.py#L145) |
| 97 | + |
| 98 | + |
| 99 | +## Base |
| 100 | + |
| 101 | +This base class uses `BaseMeta` as its meta class, hence we have all information about defined fields/properties. |
| 102 | + |
| 103 | +Then it implements |
| 104 | + - Serialization: to_dict/from_dict methods |
| 105 | + - Hierarchy: using an optional `parent_` |
| 106 | + |
| 107 | + |
| 108 | +See full implementation at [meta.py](https://github.com/threefoldtech/js-ng/blob/fa1582b83c36a8b18094fd208d04499a8d0f289d/jumpscale/core/base/meta.py#L200) |
| 109 | + |
| 110 | +Any one who uses this class as base class/model for his type, he will be able to define [custom fields](https://github.com/threefoldtech/js-ng/blob/development/jumpscale/core/base/fields.py) as class variables, then set/get a serializable and validated data. |
0 commit comments