Skip to content
This repository was archived by the owner on Jan 21, 2023. It is now read-only.

Commit 8721296

Browse files
yt-msMidnighter
authored andcommitted
refactor: extract key, title and description into an abstract base class for views
1 parent 4778f21 commit 8721296

File tree

3 files changed

+103
-25
lines changed

3 files changed

+103
-25
lines changed

src/structurizr/view/abstract_view.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# https://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
14+
"""Provide a superclass for all views."""
15+
16+
17+
from abc import ABC
18+
from typing import Dict
19+
20+
from ..abstract_base import AbstractBase
21+
from ..base_model import BaseModel
22+
from ..mixin import ViewSetRefMixin
23+
24+
25+
__all__ = ("AbstractView", "AbstractViewIO")
26+
27+
28+
class AbstractViewIO(BaseModel, ABC):
29+
"""
30+
Define an abstract base class for all views.
31+
32+
Views include static views, dynamic views, deployment views and filtered views.
33+
"""
34+
35+
key: str
36+
description: str = ""
37+
title: str = ""
38+
39+
40+
class AbstractView(ViewSetRefMixin, AbstractBase, ABC):
41+
"""
42+
Define an abstract base class for all views.
43+
44+
Views include static views, dynamic views, deployment views and filtered views.
45+
46+
"""
47+
48+
def __init__(
49+
self,
50+
*,
51+
key: str = None,
52+
description: str,
53+
title: str = "",
54+
**kwargs,
55+
):
56+
"""Initialize a view with a 'private' view set."""
57+
super().__init__(**kwargs)
58+
self.key = key
59+
self.description = description
60+
self.title = title
61+
62+
def __repr__(self) -> str:
63+
"""Return repr(self)."""
64+
return f"{type(self).__name__}(key={self.key})"
65+
66+
@classmethod
67+
def hydrate_arguments(cls, view_io: AbstractViewIO) -> Dict:
68+
"""Hydrate an AbstractViewIO into the constructor args for AbstractView."""
69+
return {
70+
"key": view_io.key,
71+
"description": view_io.description,
72+
"title": view_io.title,
73+
}

src/structurizr/view/view.py

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
"""Provide a superclass for all views."""
1717

1818

19-
from abc import ABC
2019
from typing import Any, Dict, Iterable, List, Optional, Set
2120

2221
from pydantic import Field
2322

24-
from ..abstract_base import AbstractBase
25-
from ..base_model import BaseModel
26-
from ..mixin import ViewSetRefMixin
23+
from .abstract_view import AbstractView, AbstractViewIO
2724
from ..model import Element, Model, Relationship, SoftwareSystem
2825
from .automatic_layout import AutomaticLayout, AutomaticLayoutIO
2926
from .element_view import ElementView, ElementViewIO
@@ -34,21 +31,18 @@
3431
__all__ = ("View", "ViewIO")
3532

3633

37-
class ViewIO(BaseModel, ABC):
34+
class ViewIO(AbstractViewIO):
3835
"""
39-
Define an abstract base class for all views.
36+
Define a base class for non-filtered views.
4037
4138
Views include static views, dynamic views and deployment views.
4239
"""
4340

44-
key: str
45-
description: str = ""
4641
software_system_id: Optional[str] = Field(default=None, alias="softwareSystemId")
4742
paper_size: Optional[PaperSize] = Field(default=None, alias="paperSize")
4843
automatic_layout: Optional[AutomaticLayoutIO] = Field(
4944
default=None, alias="automaticLayout"
5045
)
51-
title: str = ""
5246

5347
element_views: List[ElementViewIO] = Field(default=(), alias="elements")
5448
relationship_views: List[RelationshipViewIO] = Field(
@@ -61,9 +55,9 @@ class ViewIO(BaseModel, ABC):
6155
# )
6256

6357

64-
class View(ViewSetRefMixin, AbstractBase, ABC):
58+
class View(AbstractView):
6559
"""
66-
Define an abstract base class for all views.
60+
Define a base class for non-filtered views.
6761
6862
Views include static views, dynamic views and deployment views.
6963
@@ -73,11 +67,8 @@ def __init__(
7367
self,
7468
*,
7569
software_system: Optional[SoftwareSystem] = None,
76-
key: str = None,
77-
description: str,
7870
paper_size: Optional[PaperSize] = None,
7971
automatic_layout: Optional[AutomaticLayout] = None,
80-
title: str = "",
8172
element_views: Optional[Iterable[ElementView]] = (),
8273
relationship_views: Optional[Iterable[RelationshipView]] = (),
8374
layout_merge_strategy: Optional[Any] = None,
@@ -87,33 +78,24 @@ def __init__(
8778
super().__init__(**kwargs)
8879
self.software_system = software_system
8980
self.software_system_id = software_system.id if software_system else None
90-
self.key = key
91-
self.description = description
9281
self.paper_size = paper_size
9382
self.automatic_layout = automatic_layout
94-
self.title = title
9583
self.element_views: Set[ElementView] = set(element_views)
9684
self._relationship_views: Set[RelationshipView] = set(relationship_views)
9785

9886
# TODO
9987
self.layout_merge_strategy = layout_merge_strategy
10088

101-
def __repr__(self) -> str:
102-
"""Return repr(self)."""
103-
return f"{type(self).__name__}(key={self.key})"
104-
10589
@classmethod
10690
def hydrate_arguments(cls, view_io: ViewIO) -> Dict:
10791
"""Hydrate a ViewIO into the constructor arguments for View."""
10892
return {
93+
**super().hydrate_arguments(view_io),
10994
# TODO: should we add this here? probably not: "software_system"
110-
"key": view_io.key,
111-
"description": view_io.description,
11295
"paper_size": view_io.paper_size,
11396
"automatic_layout": AutomaticLayout.hydrate(view_io.automatic_layout)
11497
if view_io.automatic_layout
11598
else None,
116-
"title": view_io.title,
11799
"element_views": map(ElementView.hydrate, view_io.element_views),
118100
"relationship_views": map(
119101
RelationshipView.hydrate, view_io.relationship_views

tests/unit/view/test_view.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
class DerivedView(View):
2424
"""Mock class for testing."""
2525

26-
pass
26+
@classmethod
27+
def hydrate(
28+
cls,
29+
view_io: ViewIO,
30+
):
31+
"""Hydrate a DerivedView from its IO."""
32+
return cls(**cls.hydrate_arguments(view_io))
2733

2834

2935
def test_find_element_view():
@@ -173,3 +179,20 @@ def test_copy_layout():
173179
assert view2.find_element_view(element=sys1).paper_size == PaperSize.A1_Portrait
174180
rv = view2.find_relationship_view(description="Uses")
175181
assert rv.paper_size == PaperSize.A3_Portrait
182+
183+
184+
def test_hydration_includes_base_fields():
185+
"""Ensure fields from AbstractView are included when hydrating a View."""
186+
view = DerivedView(key="key", title="title", description="description")
187+
io = ViewIO.from_orm(view)
188+
189+
view2 = DerivedView.hydrate(io)
190+
assert view2.key == "key"
191+
assert view2.title == "title"
192+
assert view2.description == "description"
193+
194+
195+
def test_repr():
196+
"""Test __repr__ for views."""
197+
view = DerivedView(key="testkey", title="title", description="description")
198+
assert repr(view) == "DerivedView(key=testkey)"

0 commit comments

Comments
 (0)