style(entity): Change the world “model” by “entity”¶
Description¶
Abstract¶
Rename BaseModel and BaseModelWithId to BaseEntity and BaseEntityWithId, and replace the word “model” by “entity” everywhere, in comments and function names.
Motivation¶
To keep things consistent, it’s better to have one and only one word to name a thing, and we started with entity, and it’s the name used in the DDD word.
Rationale¶
N/A
Info¶
- Hash
c2dd0fc606636dd72c1b55d30095bbeb622b788d
- Date
2020-10-04 21:07:00 +0200
- Parents
style(mypy): Remove most “type: ignore” pragmas [76638c3d] — 2020-10-04 20:36:50 +0200
- Children
fix(entity): `id` changed from `int` to `uuid4`, renamed to `identifier` [79f704bd] — 2020-10-05 10:51:49 +0200
- Branches
- Tags
(No tags)
Changes¶
docs/domain_contexts_diagrams.py¶
- Type
Modified
- Stats
+57 -57
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-"""Make the diagrams of models for each isshub domain contexts."""
+"""Make the diagrams of entities for each isshub domain contexts."""
import importlib
import os.path
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
from attr import _Fields # pylint: disable=no-name-in-module
from isshub.domain import contexts
-from isshub.domain.utils.entity import BaseModel
+from isshub.domain.utils.entity import BaseEntity
def import_submodules(
@@ -157,13 +157,13 @@ def render_enum(enum: Type[Enum]) -> Tuple[str, str]:
)
-def validate_model(
+def validate_entity(
name: str,
- model: Type[BaseModel],
+ entity: Type[BaseEntity],
context: str,
- linkable_models: Dict[str, Type[BaseModel]],
+ linkable_entities: Dict[str, Type[BaseEntity]],
) -> Dict[str, Tuple[Any, bool]]:
- """Validate that we can handle the given model and its fields.
+ """Validate that we can handle the given entity and its fields.
We only handle fields defined with a "type hint", restricted to:
- the ones with a direct type
@@ -171,20 +171,20 @@ def validate_model(
``NoneType``)
The direct type, if in the ``isshub`` namespace, must be in the given `context` (in the given
- `linkable_models`.
+ `linkable_entities`.
Parameters
----------
name : str
- The name of the `model`
- model : Type[BaseModel]
- The model to validate
+ The name of the `entity`
+ entity : Type[BaseEntity]
+ The entity to validate
context : str
- The name of the context, ie the name of the module containing the `model` and the
- `linkable_models`
- linkable_models : Dict[str, Type[BaseModel]]
- A dict containing all the models the `model` to validate can link to, with their full python
- path as keys, and the models themselves as values
+ The name of the context, ie the name of the module containing the `entity` and the
+ `linkable_entities`
+ linkable_entities : Dict[str, Type[BaseEntity]]
+ A dict containing all the entities the `entity` to validate can link to, with their full python
+ path as keys, and the entities themselves as values
Returns
-------
@@ -198,10 +198,10 @@ def validate_model(
If the type is a ``Union`` of more than two types or with one not being ``NoneType``
TypeError
If the type is an object in the ``isshub`` namespace that is not in the given
- `linkable_models` (except for enums, actually)
+ `linkable_entities` (except for enums, actually)
"""
- types = get_type_hints(model)
+ types = get_type_hints(entity)
fields = {}
for field_name, field_type in types.items():
required = True
@@ -223,10 +223,10 @@ def validate_model(
if field_type.__module__.startswith("isshub") and not issubclass(
field_type, Enum
):
- if get_python_path(field_type) not in linkable_models:
+ if get_python_path(field_type) not in linkable_entities:
raise TypeError(
f"{name}.{field_name} : {field_type}"
- f" - It's not a valid model in context {context}"
+ f" - It's not a valid entity in context {context}"
)
fields[field_name] = (field_type, required)
@@ -241,12 +241,12 @@ def render_link(
required: bool,
attr_fields: "_Fields",
) -> str:
- """Render a link between the field of a model to another class.
+ """Render a link between the field of an entity to another class.
Parameters
----------
source_name : str
- The dot identifier of the source class. The source class is expected to be a entity model
+ The dot identifier of the source class. The source class is expected to be an entity class
field_name : str
The field in the source class that is linked to the dest class
dest_name : str
@@ -275,49 +275,49 @@ def render_link(
return f'{source_name}:{field_name} -> {dest_name}:__class__ [label="{link_label}"]'
-def render_model(
+def render_entity(
name: str,
- model: Type[BaseModel],
+ entity: Type[BaseEntity],
context: str,
- linkable_models: Dict[str, Type[BaseModel]],
+ linkable_entities: Dict[str, Type[BaseEntity]],
) -> Tuple[Dict[str, str], Set[str]]:
- """Render the given `model` to be incorporated in a dot file, with links.
+ """Render the given `entity` to be incorporated in a dot file, with links.
Parameters
----------
name : str
- The name of the `model`
- model : Type[BaseModel]
- The model to render
+ The name of the `entity`
+ entity : Type[BaseEntity]
+ The entity to render
context : str
- The name of the context, ie the name of the module containing the `model` and the
- `linkable_models`
- linkable_models : Dict[str, Type[BaseModel]]
- A dict containing all the models the `model` to validate can link to, with their full python
- path as keys, and the models themselves as values
+ The name of the context, ie the name of the module containing the `entity` and the
+ `linkable_entities`
+ linkable_entities : Dict[str, Type[BaseEntity]]
+ A dict containing all the entities the `entity` to validate can link to, with their full python
+ path as keys, and the entities themselves as values
Returns
-------
Dict[str, str]
- Lines representing the models (or enums) to render in the graph.
- The keys are the dot identifier of the model (or enum), and the values are the line to put
+ Lines representing the entities (or enums) to render in the graph.
+ The keys are the dot identifier of the entity (or enum), and the values are the line to put
in the dot file to render them.
- There is at least one entry, the rendered `model`, but there can be more entries, if the
- `model` is linked to some enums (we use a dict to let the caller to deduplicate enums with
- the same identifiers if called from many models)
+ There is at least one entry, the rendered `entity`, but there can be more entries, if the
+ `entity` is linked to some enums (we use a dict to let the caller to deduplicate enums with
+ the same identifiers if called from many entities)
Set[str]
- Lines representing the links between the `model` and other models or enums.
+ Lines representing the links between the `entity` and other entities or enums.
"""
lines = {}
links = set()
dot_name = get_dot_identifier(name)
- attr_fields = attr.fields(model)
+ attr_fields = attr.fields(entity)
fields = {}
- for field_name, (field_type, required) in validate_model(
- name, model, context, linkable_models
+ for field_name, (field_type, required) in validate_entity(
+ name, entity, context, linkable_entities
).items():
link_to = None
@@ -341,50 +341,50 @@ def render_model(
)
lines[
dot_name
- ] = f'{dot_name} [label="<__class__> Model: {model.__name__}|{fields_parts}"]'
+ ] = f'{dot_name} [label="<__class__> Entity: {entity.__name__}|{fields_parts}"]'
return lines, links
def make_domain_context_graph(
- context_name: str, subclasses: Dict[str, Type[BaseModel]], output_path: str
+ context_name: str, subclasses: Dict[str, Type[BaseEntity]], output_path: str
) -> None:
- """Make the graph of models in the given contexts.
+ """Make the graph of entities in the given contexts.
Parameters
----------
context_name : str
The name of the context, represented by the python path of its module
- subclasses : Dict[str, Type[BaseModel]]
- All the subclasses of ``BaseModel`` from which to extract the modules to render.
+ subclasses : Dict[str, Type[BaseEntity]]
+ All the subclasses of ``BaseEntity`` from which to extract the modules to render.
Only subclasses present in the given context will be rendered.
output_path : str
The path where to save the generated graph
"""
- # restrict the subclasses of ``BaseModel`` to the ones in the given module name
+ # restrict the subclasses of ``BaseEntity`` to the ones in the given module name
context_subclasses = {
subclass_name: subclass
for subclass_name, subclass in subclasses.items()
if subclass_name.startswith(context_name + ".")
}
- # render models and all links between them
- model_lines, links = {}, set()
+ # render entities and all links between them
+ entity_lines, links = {}, set()
for subclass_name, subclass in context_subclasses.items():
- subclass_lines, subclass_links = render_model(
+ subclass_lines, subclass_links = render_entity(
subclass_name,
subclass,
context_name,
context_subclasses,
)
- model_lines.update(subclass_lines)
+ entity_lines.update(subclass_lines)
links.update(subclass_links)
# compose the content of the dot file
dot_file_content = (
"""\
-digraph domain_context_models {
+digraph domain_context_entities {
label = "Domain context [%s]"
#labelloc = "t"
rankdir=LR
@@ -392,7 +392,7 @@ digraph domain_context_models {
"""
% context_name
)
- for line in tuple(model_lines.values()) + tuple(links):
+ for line in tuple(entity_lines.values()) + tuple(links):
dot_file_content += f" {line}\n"
dot_file_content += "}"
@@ -403,7 +403,7 @@ digraph domain_context_models {
def make_domain_contexts_diagrams(output_path: str) -> None:
- """Make the diagrams of models for each domain contexts.
+ """Make the diagrams of entities for each domain contexts.
Parameters
----------
@@ -411,9 +411,9 @@ def make_domain_contexts_diagrams(output_path: str) -> None:
The path where to save the generated diagrams
"""
- # we need to import all python files (except tests) to find all submodels of ``BaseModel``
+ # we need to import all python files (except tests) to find all subclasses of ``BaseEntity``
import_submodules(contexts, skip_names=["tests"])
- subclasses = get_final_subclasses(BaseModel)
+ subclasses = get_final_subclasses(BaseEntity)
# we render each context independently, assuming that each one is directly at the root of
# the ``contexts`` package
isshub/domain/contexts/code_repository/__init__.py¶
- Type
Modified
- Stats
+1 -1
@@ -1,6 +1,6 @@
"""Package to handle isshub domain code_repository context.
-The "code_repository" context defines every models that are related to code repositories (like
+The "code_repository" context defines every entities that are related to code repositories (like
Github, Gitlab...):
- repositories
isshub/domain/contexts/code_repository/entities/namespace/__init__.py¶
- Type
Modified
- Stats
+2 -2
@@ -4,7 +4,7 @@ import enum
from typing import Any, Optional
from isshub.domain.utils.entity import (
- BaseModelWithId,
+ BaseEntityWithId,
field_validator,
optional_field,
required_field,
@@ -21,7 +21,7 @@ class NamespaceKind(enum.Enum):
@validated()
-class Namespace(BaseModelWithId):
+class Namespace(BaseEntityWithId):
"""A namespace can contain namespaces and repositories.
Attributes
isshub/domain/contexts/code_repository/entities/repository/__init__.py¶
- Type
Modified
- Stats
+2 -2
@@ -1,11 +1,11 @@
"""Package defining the ``Repository`` entity."""
from isshub.domain.contexts.code_repository.entities.namespace import Namespace
-from isshub.domain.utils.entity import BaseModelWithId, required_field, validated
+from isshub.domain.utils.entity import BaseEntityWithId, required_field, validated
@validated()
-class Repository(BaseModelWithId):
+class Repository(BaseEntityWithId):
"""A repository holds code, issues, code requests...
Attributes
isshub/domain/utils/entity.py¶
- Type
Modified
- Stats
+44 -42
@@ -31,7 +31,7 @@ else:
class _InstanceOfSelfValidator(
attr.validators._InstanceOfValidator # type: ignore # pylint: disable=protected-access
):
- """Validator checking that the field holds an instance of its own model."""
+ """Validator checking that the field holds an instance of its own entity."""
def __call__(self, inst, attr, value): # type: ignore # pylint: disable=redefined-outer-name
"""Validate that the `value` is an instance of the class of `inst`.
@@ -43,7 +43,7 @@ class _InstanceOfSelfValidator(
def instance_of_self() -> _InstanceOfSelfValidator:
- """Return a validator checking that the field holds an instance of its own model.
+ """Return a validator checking that the field holds an instance of its own entity.
Returns
-------
@@ -61,10 +61,11 @@ def optional_field(
Parameters
----------
field_type : Union[type, str]
- The expected type of the field. Use the string "self" to reference the current field's model
+ The expected type of the field. Use the string "self" to reference the current field's
+ entity
relation_verbose_name : Optional[str]
- A verbose name to describe the relation between the model linked to the field, and the
- model pointed by `field_type`
+ A verbose name to describe the relation between the entity linked to the field, and the
+ entity pointed by `field_type`
Returns
-------
@@ -79,14 +80,14 @@ def optional_field(
Examples
--------
- >>> from isshub.domain.utils.entity import optional_field, validated, BaseModel
+ >>> from isshub.domain.utils.entity import optional_field, validated, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: str = optional_field(str)
>>>
>>> from isshub.domain.utils.testing.validation import check_field_nullable
- >>> check_field_nullable(MyModel, 'my_field', my_field='foo')
+ >>> check_field_nullable(MyEntity, 'my_field', my_field='foo')
"""
metadata = {}
@@ -116,14 +117,15 @@ def required_field(
Parameters
----------
field_type : Union[type, str]
- The expected type of the field. Use the string "self" to reference the current field's model
+ The expected type of the field. Use the string "self" to reference the current field's
+ entity
frozen : bool
If set to ``False`` (the default), the field can be updated after being set at init time.
If set to ``True``, the field can be set at init time but cannot be changed later, else a
``FrozenAttributeError`` exception will be raised.
relation_verbose_name : Optional[str]
- A verbose name to describe the relation between the model linked to the field, and the
- model pointed by `field_type`
+ A verbose name to describe the relation between the entity linked to the field, and the
+ entity pointed by `field_type`
Returns
-------
@@ -137,14 +139,14 @@ def required_field(
Examples
--------
- >>> from isshub.domain.utils.entity import required_field, validated, BaseModel
+ >>> from isshub.domain.utils.entity import required_field, validated, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: str = required_field(str)
>>>
>>> from isshub.domain.utils.testing.validation import check_field_not_nullable
- >>> check_field_not_nullable(MyModel, 'my_field', my_field='foo')
+ >>> check_field_not_nullable(MyEntity, 'my_field', my_field='foo')
"""
metadata = {}
@@ -179,20 +181,20 @@ def validated() -> Any:
Examples
--------
- >>> from isshub.domain.utils.entity import required_field, validated, BaseModel
+ >>> from isshub.domain.utils.entity import required_field, validated, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: str = required_field(str)
>>>
- >>> MyModel.__slots__
+ >>> MyEntity.__slots__
('my_field',)
>>>
- >>> instance = MyModel()
+ >>> instance = MyEntity()
Traceback (most recent call last):
...
TypeError: __init__() missing 1 required keyword-only argument: 'my_field'
- >>> instance = MyModel(my_field='foo')
+ >>> instance = MyEntity(my_field='foo')
>>> instance.my_field
'foo'
>>> instance.validate()
@@ -226,10 +228,10 @@ class field_validator: # pylint: disable=invalid-name
Examples
--------
- >>> from isshub.domain.utils.entity import field_validator, required_field, BaseModel
+ >>> from isshub.domain.utils.entity import field_validator, required_field, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: str = required_field(str)
...
... @field_validator(my_field)
@@ -237,18 +239,18 @@ class field_validator: # pylint: disable=invalid-name
... if value != 'foo':
... raise ValueError(f'{self.__class__.__name__}.my_field must be "foo"')
>>>
- >>> instance = MyModel(my_field='bar')
+ >>> instance = MyEntity(my_field='bar')
Traceback (most recent call last):
...
- ValueError: MyModel.my_field must be "foo"
- >>> instance = MyModel(my_field='foo')
+ ValueError: MyEntity.my_field must be "foo"
+ >>> instance = MyEntity(my_field='foo')
>>> instance.my_field
'foo'
>>> instance.my_field = 'bar'
>>> instance.validate()
Traceback (most recent call last):
...
- ValueError: MyModel.my_field must be "foo"
+ ValueError: MyEntity.my_field must be "foo"
>>> instance.my_field = 'foo'
>>> instance.validate()
>>> instance.my_field
@@ -292,13 +294,13 @@ def validate_instance(instance: Any) -> Any:
Examples
--------
- >>> from isshub.domain.utils.entity import required_field, validate_instance, BaseModel
+ >>> from isshub.domain.utils.entity import required_field, validate_instance, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: str = required_field(str)
>>>
- >>> instance = MyModel(my_field='foo')
+ >>> instance = MyEntity(my_field='foo')
>>> validate_instance(instance)
>>> instance.my_field = None
>>> validate_instance(instance)
@@ -333,10 +335,10 @@ def validate_positive_integer(
Examples
--------
- >>> from isshub.domain.utils.entity import field_validator, required_field, BaseModel
+ >>> from isshub.domain.utils.entity import field_validator, required_field, BaseEntity
>>>
>>> @validated()
- ... class MyModel(BaseModel):
+ ... class MyEntity(BaseEntity):
... my_field: int = required_field(int)
...
... @field_validator(my_field)
@@ -347,28 +349,28 @@ def validate_positive_integer(
... display_name=f"{self.__class__.__name__}.my_field",
... )
>>>
- >>> instance = MyModel(my_field='foo')
+ >>> instance = MyEntity(my_field='foo')
Traceback (most recent call last):
...
TypeError: ("'my_field' must be <class 'int'> (got 'foo' that is a <class 'str'>)...
- >>> instance = MyModel(my_field=-2)
+ >>> instance = MyEntity(my_field=-2)
Traceback (most recent call last):
...
- ValueError: MyModel.my_field must be a positive integer
- >>> instance = MyModel(my_field=0)
+ ValueError: MyEntity.my_field must be a positive integer
+ >>> instance = MyEntity(my_field=0)
Traceback (most recent call last):
...
- ValueError: MyModel.my_field must be a positive integer
- >>> instance = MyModel(my_field=1.1)
+ ValueError: MyEntity.my_field must be a positive integer
+ >>> instance = MyEntity(my_field=1.1)
Traceback (most recent call last):
...
TypeError: ("'my_field' must be <class 'int'> (got 1.1 that is a <class 'float'>)...
- >>> instance = MyModel(my_field=1)
+ >>> instance = MyEntity(my_field=1)
>>> instance.my_field = -2
>>> instance.validate()
Traceback (most recent call last):
...
- ValueError: MyModel.my_field must be a positive integer
+ ValueError: MyEntity.my_field must be a positive integer
"""
if none_allowed and value is None:
@@ -381,8 +383,8 @@ def validate_positive_integer(
@validated()
-class BaseModel:
- """A base model without any field, that is able to validate itself."""
+class BaseEntity:
+ """A base entity without any field, that is able to validate itself."""
def validate(self) -> None:
"""Validate all fields of the current instance.
@@ -397,8 +399,8 @@ class BaseModel:
@validated()
-class BaseModelWithId(BaseModel):
- """A base model with an ``id``, that is able to validate itself.
+class BaseEntityWithId(BaseEntity):
+ """A base entity with an ``id``, that is able to validate itself.
Attributes
----------
isshub/domain/utils/testing/validation.py¶
- Type
Modified
- Stats
+9 -9
@@ -6,7 +6,7 @@ import pytest
from attr.exceptions import FrozenAttributeError
-from isshub.domain.utils.entity import BaseModel
+from isshub.domain.utils.entity import BaseEntity
ValuesValidation = List[Tuple[Any, Optional[Type[Exception]]]]
@@ -30,12 +30,12 @@ positive_integer_only: ValuesValidation = integer_only + no_zero
string_only: ValuesValidation = [("foo", None), (1, TypeError), (-0.1, TypeError)]
-def check_field(obj: BaseModel, field_name: str) -> None:
+def check_field(obj: BaseEntity, field_name: str) -> None:
"""Assert that the given `obj` has an attribute named `field_name`.
Parameters
----------
- obj : BaseModel
+ obj : BaseEntity
The object to test
field_name : str
The field name to search for
@@ -50,7 +50,7 @@ def check_field(obj: BaseModel, field_name: str) -> None:
def check_field_value(
- factory: Callable[..., BaseModel],
+ factory: Callable[..., BaseEntity],
field_name: str,
value: Any,
exception: Optional[Type[Exception]],
@@ -60,7 +60,7 @@ def check_field_value(
Parameters
----------
- factory : Callable[...,BaseModel]
+ factory : Callable[...,BaseEntity]
The factory to use to create the object to test
field_name : str
The name of the field to check
@@ -109,13 +109,13 @@ def check_field_value(
def check_field_not_nullable(
- factory: Callable[..., BaseModel], field_name: str, **factory_kwargs: Any
+ factory: Callable[..., BaseEntity], field_name: str, **factory_kwargs: Any
) -> None:
"""Assert that an object cannot have a specific field set to ``None``.
Parameters
----------
- factory : Callable[...,BaseModel]
+ factory : Callable[...,BaseEntity]
The factory to use to create the object to test
field_name : str
The name of the field to check
@@ -147,13 +147,13 @@ def check_field_not_nullable(
def check_field_nullable(
- factory: Callable[..., BaseModel], field_name: str, **factory_kwargs: Any
+ factory: Callable[..., BaseEntity], field_name: str, **factory_kwargs: Any
) -> None:
"""Assert that an object can have a specific field set to ``None``.
Parameters
----------
- factory : Callable[...,BaseModel]
+ factory : Callable[...,BaseEntity]
The factory to use to create the object to test
field_name : str
The name of the field to check