- Notifications
You must be signed in to change notification settings - Fork 16
Add filtered views #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f591b37 bd07b10 5d4ffda 6247201 fb76f5b b393557 ae482b2 056684a File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| | ||
| | ||
| """Provide a superclass for all views.""" | ||
| | ||
| | ||
| from abc import ABC | ||
| from typing import Dict | ||
| | ||
| from ..abstract_base import AbstractBase | ||
| from ..base_model import BaseModel | ||
| from ..mixin import ViewSetRefMixin | ||
| | ||
| | ||
| __all__ = ("AbstractView", "AbstractViewIO") | ||
| | ||
| | ||
| class AbstractViewIO(BaseModel, ABC): | ||
| """ | ||
| Define an abstract base class for all views. | ||
| | ||
| Views include static views, dynamic views, deployment views and filtered views. | ||
| """ | ||
| | ||
| key: str | ||
| description: str = "" | ||
| title: str = "" | ||
| | ||
| | ||
| class AbstractView(ViewSetRefMixin, AbstractBase, ABC): | ||
| """ | ||
| Define an abstract base class for all views. | ||
| | ||
| Views include static views, dynamic views, deployment views and filtered views. | ||
| | ||
| """ | ||
| | ||
| def __init__( | ||
| self, | ||
| *, | ||
| key: str = None, | ||
| description: str, | ||
| title: str = "", | ||
| **kwargs, | ||
| ): | ||
| """Initialize a view with a 'private' view set.""" | ||
| super().__init__(**kwargs) | ||
| self.key = key | ||
| self.description = description | ||
| self.title = title | ||
| | ||
| def __repr__(self) -> str: | ||
| """Return repr(self).""" | ||
| return f"{type(self).__name__}(key={self.key})" | ||
| | ||
| @classmethod | ||
| def hydrate_arguments(cls, view_io: AbstractViewIO) -> Dict: | ||
| """Hydrate an AbstractViewIO into the constructor args for AbstractView.""" | ||
| return { | ||
| "key": view_io.key, | ||
| "description": view_io.description, | ||
| "title": view_io.title, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| | ||
| | ||
| """Provide a filtered view.""" | ||
| | ||
| from enum import Enum | ||
| from typing import Iterable, List, Optional, Union | ||
| | ||
| from ordered_set import OrderedSet | ||
| from pydantic import Field, validator | ||
| | ||
| from .abstract_view import AbstractView, AbstractViewIO | ||
| from .static_view import StaticView | ||
| | ||
| | ||
| __all__ = ("FilteredView", "FilteredViewIO") | ||
| | ||
| | ||
| class FilterMode(Enum): | ||
| """indicates whether elements/relationships are being included or excluded.""" | ||
| | ||
| Include = "Include" | ||
| Exclude = "Exclude" | ||
| | ||
| | ||
| class FilteredViewIO(AbstractViewIO): | ||
| """ | ||
| Represent the FilteredView from the C4 model. | ||
| | ||
| Attributes: | ||
| base_view_key: The key of the view on which this filtered view is based. | ||
| mode: Whether elements/relationships are being included or excluded based | ||
| upon the set of tag | ||
| tags: The set of tags to include/exclude elements/relationships when rendering | ||
| this filtered view. | ||
| | ||
| Note that unlike Model Items, when filtered view tags are serialised to JSON then | ||
| they are serialised as an array rather than comma-separated. | ||
| """ | ||
| | ||
| base_view_key: str = Field(alias="baseViewKey") | ||
| mode: FilterMode | ||
| tags: List[str] | ||
| Owner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take a look at Collaborator Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, the API is inconsistent, and for FilteredView the tags are serialised in JSON as an array. I've added the validator anyway for safety. Collaborator Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will try this against an actual Structurizr instance to make sure the Swagger is correct though... Collaborator Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I built a filtered view in Structurizr and exported the JSON, and it does indeed serialise the tags as an array rather than comma-separated. Owner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh, thanks for digging into that. @simonbrowndotje is that an oversight or fully intentional? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was intentional at one point in time, but not any more ... when I first started writing the Java client library (5+ years ago), I had some issues with the order of array elements being preserved. The order for tags defining a filtered view isn't important, but the ordering of element/relationship tags is. So the former is an array, whereas the latter is a comma-separated list. | ||
| | ||
| @validator("tags", pre=True) | ||
| def split_tags(cls, tags: Union[str, Iterable[str]]) -> List[str]: | ||
| """Convert comma-separated tag list into list if needed.""" | ||
| if isinstance(tags, str): | ||
| return tags.split(",") | ||
| return list(tags) | ||
| | ||
| | ||
| class FilteredView(AbstractView): | ||
| """ | ||
| Represent the filtered view from the C4 model. | ||
| | ||
| A FilteredView is a view that is based on another view, but including or excluding | ||
| elements and relationships from the base view according to their tags. | ||
| | ||
| Attributes: | ||
| view: the view which this FilteredView is based on | ||
| base_view_key: The key of the view on which this filtered view is based. | ||
| mode: Whether elements/relationships are being included or excluded based | ||
| upon the set of tag | ||
| tags: The set of tags to include/exclude elements/relationships when rendering | ||
| this filtered view. | ||
| """ | ||
| | ||
| def __init__( | ||
| self, | ||
| mode: FilterMode, | ||
| tags: Iterable[str], | ||
| view: Optional[StaticView] = None, | ||
| base_view_key: Optional[str] = None, | ||
| **kwargs | ||
| ) -> None: | ||
| """Initialize a filtered view.""" | ||
| super().__init__(**kwargs) | ||
| self._base_view_key = base_view_key | ||
| self.view = view | ||
| self.mode = mode | ||
| self.tags = OrderedSet(tags) | ||
| | ||
| @property | ||
| def base_view_key(self) -> str: | ||
| """Return the key of the base view.""" | ||
| return self.view.key if self.view else self._base_view_key | ||
| | ||
| @classmethod | ||
| def hydrate( | ||
| cls, | ||
| view_io: FilteredViewIO, | ||
| ) -> "FilteredView": | ||
| """Hydrate a new FilteredView instance from its IO.""" | ||
| return cls( | ||
| **cls.hydrate_arguments(view_io), | ||
| base_view_key=view_io.base_view_key, | ||
| mode=view_io.mode, | ||
| tags=view_io.tags | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not very happy with how we construct these arguments but that's neither here nor there.