GithubHelp home page GithubHelp logo

midnighter / structurizr-python Goto Github PK

View Code? Open in Web Editor NEW
65.0 10.0 16.0 612 KB

A Python 3 port of Simon Brown's Structurizr diagrams-as-code architecture description tool.

Home Page: https://structurizr.com/

License: Apache License 2.0

Python 99.48% Makefile 0.52%
structurizr structurizr-api c4-model software-architecture diagrams-as-code

structurizr-python's Introduction

Structurizr for Python

Current PyPI Version Supported Python Versions Apache Software License Version 2.0 Code of Conduct GitHub Actions Codecov Black Documentation Status

A Python client package for the Structurizr cloud service and on-premises installation.

Archive Mode

Take Note: I realized that I rely more and more on the Structurizr DSL and less on this Python client. Since this is a hobby project and my time is limited, I decided that I will stop supporting this package for now and put it into archive mode. If you depend on this project, you can always install the last version from PyPI; and if you want to pick up maintenance & development, please contact me by email or on the Structurizr Slack #python channel. I will be happy to introduce you to the codebase and assist in any way I can.

Warning

The structurizr-python package is in active development and should be considered Alpha software. Reports of problems are appreciated but please do not expect fully working software at this point. If you want to get involved, you are most welcome as this is a spare time project. Please write me an e-mail or on the Structurizr Slack team so that we can coordinate.

Install

It's as simple as:

pip install structurizr-python

Copyright

structurizr-python's People

Contributors

ilaif avatar midnighter avatar yt-ms avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

structurizr-python's Issues

pip install fails on Linux

Problem description

Please explain:
What & how: Tried to install structurizr-python using the pip instructions on the github repo
Why: could not install it:

pip install --pre structurizr-python seems to be failing on Linux:

Could not find a version that satisfies the requirement structurizr-python (from versions: )
No matching distribution found for structurizr-python

Improve syntax for the client locking a workspace as a context manager

Checklist

Is your feature related to a problem? Please describe it.

Currently, you can lock a workspace with syntax like this:

    client = StructurizrClient(settings=settings)
    with client:
        client.put_workspace(workspace)

This is cool, but a little unintuitive that it's actually taking a lock out. I think it would be better to be more explicit about what's going on:

    client = StructurizrClient(settings=settings)
    with client.lock():
        client.put_workspace(workspace)

I can't decide whether it is better to have lock as a method or a property, but am leaning toward method as shown.

How to enable auto layout

Checklist

Question

Hi, is it currently already somehow possible to use the auto-layout functionality for the diagrams, and if yes, how can I do that?

Thanks,
Falko

Autolayout doesn't support resizePaper and margin

Checklist

Is your feature related to a problem? Please describe it.

Currently auto layout can be set with the following code:

workspace.views.automatic_layout = AutomaticLayout(
            rank_direction=RankDIrection.TopBottom,
            rank_separation=300,
            edge_separation=10,
            node_separation=300,
            vertices=False
        )

but it only support the following parameters

rank_direction
rank_separation
node_separation
edge_separation
vertices

rank_direction: RankDirection = Field(..., alias="rankDirection")
rank_separation: int = Field(..., alias="rankSeparation")
node_separation: int = Field(..., alias="nodeSeparation")
edge_separation: int = Field(..., alias="edgeSeparation")
vertices: bool

But structurizr.com supports more than that. For example resize paper

https://graphviz.structurizr.com/?view=id&resizePaper=true&rankDirection=TB&rankSeparation=300&nodeSeparation=300&margin=400

Describe the solution you would like.

To add resizePaper and margin params support to AutomaticLayout class

Describe alternatives you considered

Manually auto layout on structurizr.com

Additional context

Constrain elements that can be added to each view type

Checklist

Is your feature related to a problem? Please describe it.

In the Java implementation, each view class overrides checkElementCanBeAdded which is defined abstractly in View. Without having this, it is possible to add elements to a view that then would not be supported in Structurizr itself.

Describe the solution you would like.

Mirror the Java implementation with an abstract check_element_can_be_added method in View which each subtype overrides.

Additional context

from structurizr import Workspace
workspace = Workspace(name="Test", description="Test")
system = workspace.model.add_software_system(name="System 1")
container = system.add_container(name="Container 1")
view = workspace.views.create_container_view(key="con1", description="test", software_system=system)

# This should fail but doesn't
view.add(system)

Refactor model as an argument to hydrate class methods

Yeah - I was trying to establish a consistent pattern across SoftwareSystem, Container, Component and DeploymentNode. A couple of alternative approaches:

  • In Model, when a SoftwareSystem or DeploymentNode is added then it Model could explicitly add all the children. This would be made easier if we added a children property to ModelItem which by default returned an empty list but could be overridden by subtypes (e.g. SoftwareSystem would return its containers, etc.).
  • In SoftwareSystem, etc. we override set_model (from ModelRefMixin) to also set the model in its containers, etc.

I'm leaning toward the first approach (including the generic children) as it feels less fragile.

Originally posted by @yt-ms in #41 (comment)

Allow name and description arguments to elements to be positional

Checklist

Is your feature related to a problem? Please describe it.

When constructing a model from Python (rather than manipulating an existing one), it would be natural and more compact to be able to specify both the name and description of an element positionally rather than having to explicitly name them:

c1 = Container("Mobile app", "Application running on mobile devices")

See also #13

Describe the solution you would like.

class Container(StaticStructureElement):
    def __init__(
        self,
        name: str = "",
        desctription: str = "",
        *,
        parent: "SoftwareSystem" = None,
        technology: str = "",
        components: Iterable[Component] = (),
        **kwargs
    )

ContainerViewIO should not suppress externalSoftwareSystemBoundariesVisible in JSON when true.

Problem description

When creating a ContainerView, the default for external_software_system_boundaries_visible is True, which makes sense. When uploading a view to Structurizr however, the ContainerViewIO suppresses this field if it is True (because that is specified as the default), and then Structurizr interprets that as false so the boundaries are not actually shown.

Code Sample

>>> from structurizr.view.container_view import ContainerView, ContainerViewIO
>>> cv = ContainerView(key="key", description="description")
>>> io = ContainerViewIO(cv)
>>> io = ContainerViewIO.from_orm(cv)
>>> io.json()
'{"key": "key", "description": "description", "elements": [], "relationships": []}'
>>>

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                              1.5.4
httpx                               0.16.1
importlib_metadata                   1.7.0
ordered-set                            3.1
pip                                 20.2.4
pydantic                             1.7.1
python-dotenv                       0.14.0
setuptools                          41.2.0
structurizr-python 0.2.1+29.gf1b13ef.dirty

failed to put_workspace

Problem description

Please explain:

  • what you tried to achieve,
    I have I freshly created workspace at structurizr, and used your getting started example to build a basic workspace. I then worked out the environment variables required from your unit tests in order to specify my workspace and appropriate keys (hidden in my attached stack trace)

I expected to see the getting started diagrams in my new workspace on successful call of put_workspace, but the call fails with the attached callstack.

Code Sample

The following is shows what I changed in getting started to push up the workspace:

if __name__ == "__main__":
    logging.basicConfig(level="INFO")
    ws = main()
    settings = StructurizrClientSettings()
    ws.id = settings.workspace_id
    structurizr.show_versions()
    print(settings)
    scli = StructurizrClient(settings=settings)
    scli.put_workspace(ws)

/home/mjc/code/python/architecture/venv/bin/python /home/mjc/code/python/architecture/getting_started.py

System Information
==================
OS                  Linux
OS-release 5.6.15-arch1-1
Python              3.8.3

Package Versions
================
depinfo              1.5.3
httpx               0.13.3
pip                 20.1.1
pydantic             1.5.1
python-dotenv       0.13.0
setuptools          47.1.1
structurizr-python 0.0.1a1
url=HttpUrl('https://api.structurizr.com', scheme='https', host='api.structurizr.com', tld='com', host_type='domain') workspace_id=***** api_key=UUID('****') api_secret=UUID('****') user='**@**.**' agent='structurizr-python/0.0.1a1' workspace_archive_location=PosixPath('/home/mjc/code/python/architecture')
Traceback (most recent call last):
  File "/home/mjc/code/python/architecture/getting_started.py", line 69, in <module>
    scli.put_workspace(ws)
  File "/home/mjc/.local/lib/python3.8/site-packages/structurizr/api/structurizr_client.py", line 161, in put_workspace
    workspace_json = ws_io.json(by_alias=True, exclude_none=True)
  File "/home/mjc/.local/lib/python3.8/site-packages/structurizr/base_model.py", line 79, in json
    return super().json(
  File "pydantic/main.py", line 423, in pydantic.main.BaseModel.json
  File "/home/mjc/.local/lib/python3.8/site-packages/structurizr/base_model.py", line 55, in dict
    return super().dict(
  File "pydantic/main.py", line 386, in pydantic.main.BaseModel.dict
  File "pydantic/main.py", line 698, in _iter
  File "pydantic/main.py", line 755, in pydantic.main.BaseModel.__eq__
  File "/home/mjc/.local/lib/python3.8/site-packages/structurizr/base_model.py", line 55, in dict
    return super().dict(
  File "pydantic/main.py", line 386, in pydantic.main.BaseModel.dict
  File "pydantic/main.py", line 706, in _iter
  File "pydantic/main.py", line 639, in pydantic.main.BaseModel._get_value
  File "pydantic/main.py", line 640, in genexpr
  File "pydantic/main.py", line 607, in pydantic.main.BaseModel._get_value
  File "/home/mjc/.local/lib/python3.8/site-packages/structurizr/model/model_item.py", line 58, in dict
    obj["tags"] = ",".join(obj["tags"])
KeyError: 'tags'

Process finished with exit code 1

Context

See output above.

Add support for the group element in C4 DSL

Checklist

Is your feature related to a problem? Please describe it.

I want to model an architecture with several different stakeholders in a partnership, and wanted to group components provided from/developed by each partner.

Describe the solution you would like.

An implementation of the group elemement (see dsl language reference)

Describe alternatives you considered

I can make use of the DSL or maintain "manual" diagrams for the landscape and potentially context level.

Additional context

Example can be rendered at https://structurizr.com/dsl demo site.

Problem with creating a system context view

When trying to create a system context view and add an already created software system (using the same demo for financial risk system), I experience problems on this line:

    contextView = views.create_system_context_view(
        software_system=financial_risk_system,
        key="Context",
        description=(
            "An example System Context diagram for the Financial Risk System "
            "architecture kata."
        ),
    )

The error i got is the following:

svs_software_system=SoftwareSystem(id=2, name=Financial Risk System)

Add `add_default_elements()` method to view classes

Checklist

Is your feature related to a problem? Please describe it.

In keeping with the Java implementation, each static view type should have an addDefaultElements() method which adds in the default elements for that view type - e.g. for container views this would add all the containers in the system and their nearest neighbours (see ContainerView.java for an example).

Describe the solution you would like.

Abstract method in StaticView, implemented in each of the derived types.

Add support for Dynamic views

Checklist

Is your feature related to a problem? Please describe it.

The Python API is currently missing support for Dynamic Views, which are useful for showing the sequence of collaboration between elements at runtime.

Big Bank dynamic view

Describe the solution you would like.

The Java implementation on which this should probably be based is here. Note that there are a couple of bits of complexity in here:

  • Logic around what is allowed to be added, depending on the scope of the Dynamic View (*, System, Container).
  • Logic to find which relationship to add to the view, including looking at reverse-direction relationships compared to what was specified.
  • There's a comment in the DSL reference that if the relationship doesn't already exist in the model then it'll be added automatically, although I couldn't see evidence of this in the core Java implementation.

The order attribute of RelationshipView which is needed for this is already present.

Improve serialization integration tests

Our current serialization integration tests check that what we implemented is serialized the same from code and from DSL (JSON) as a Pydantic IO representation.

We need to add a test to checks that a valid JSON DSL can be created from code, to verify that the features in the JSON file are fully implemented.

StructurizrClient.get_workspace() breaks on request.url.full_path

Problem description

When invoking StructurizrClient.get_workspace(), an error occurs because httpx.URL does not have an attribute full_path.

Checking the source, this definitely does not exist.

Code Sample

Create a minimal, complete, verifiable example.

from structurizr.api import StructurizrClientSettings, StructurizrClient
settings = StructurizrClientSettings(url="http://a.b.c.com:8080/api", workspace_id=1, api_key="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", api_secret="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", user="structurizr")
client = StructurizrClient(settings=settings)
client.get_workspace()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\users\tordoff\dev\structurizr-python\src\structurizr\api\structurizr_client.py", line 136, in get_workspace
    request.headers.update(self._add_headers(request.url.full_path))
AttributeError: 'URL' object has no attribute 'full_path'

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                                 1.5.4
httpx                                  0.15.3
ordered-set                               3.1
pip                                    20.2.3
pydantic                                1.6.1
python-dotenv                          0.14.0
setuptools                             41.2.0
structurizr-python 0.0.1rc4+98.g8268b96.dirty

Problem with adding a person to the existing workspace

I've been trying to integrate Structurizr into a Python script, but it seems it has some problems I could not figure out so far.

I created a dummy empty workspace on Structurizr and when trying to connect and add elements, it fails on adding a person.

Line that fails:

user = model.add_person("User", "A user of my software system.")

Error stack:

Traceback (most recent call last):
  File "/snap/pycharm-community/252/plugins/python-ce/helpers/pydev/pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/snap/pycharm-community/252/plugins/python-ce/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/velimir/smart_voucher_selection/structurizr_init.py", line 13, in <module>
    kam_user = model.add_person("User", "A user of my software system.")
TypeError: add_person() takes from 1 to 2 positional arguments but 3 were given
python-BaseException

Syntactic sugar around relationship creation

Checklist

Is your feature related to a problem? Please describe it.

Currently, to add a relationship between two elements is a little clunky (mirroring the Java API):

c1 = Container("Container 1")
c2 = Container("Container 2")
r = c1.add_relationship(destination=c2, description="Sends events to")

I think we can do better in Python.

Describe the solution you would like.

By overriding __rshift__ and __irshift__ on Element then we can achieve something much cleaner:

c1 = Container("Container 1")
c2 = Container("Container 2")
c1 >> "Sends events to" >> c2

The result of this expression would be the Relationship so you could continue to add technologies, tags, etc. We could also provide a shortcut that creates a general relationship with description "Uses":

c1 >> c2

And we should also support constructing the relationship explicitly:

c1 >> Relationship("Sends events to", technologies="Kafka") >> c2

This becomes even more useful if people choose to subtype Relationship:

c1 >> Kafka("Sends events to", topic="eventStream") >> c2

(here the topic would be added to the properties collection of the Relationship)

How do I get data into or out of an existing workspace?

Checklist

Question

If you have started sketching out a workspace in structurisr.com, how do you pull down an existing project, to work on locally?

Conversely, once you've made changes, how do you push it back up? I was expecting to see a put or get method, based on the web API docs for structurizr.

Some code examples for this would be really helpful.

Support association of child elements through Python context blocks

Checklist

Is your feature related to a problem? Please describe it.

Constructing a tree of elements from Python is quite verbose today:

system = model.add_software_system(name="My System")
container1 = system.add_container(name="Container 1")
component1 = container1.add_component(name="Component 1")
component2 = container1.add_component(name="Component 2")
component1.add_relationship(description="Sends events to", destination=component2)
etc.

Describe the solution you would like.

Using Python context, we could achieve the same outcome in an easier to read manner:

with model:
  with SoftwareSystem("My System"):
    with Container("Container 1") as container1:
      Component("Component 1") >> "Sends events to" >> Component("Component 2")

This would involve establishing appropriate ContextVars in __enter__()/__exit__() and checking for those during constructors to auto-register via the parent's add().

Additional context

This is inspired by mingrammer/diagrams.

Implement DeploymentView

Checklist

Is your feature related to a problem? Please describe it.

Following on from the main body of #8 (Implement models, views and styles), we do not currently have an implementation for DeploymentView. One consequence of this is that if you load a workspace from Structurizr into Python, do some stuff to it (e.g. add more elements or update tags/properteis) and then push it back to Structurizr, you will lose any deployment views that you had created.

Describe the solution you would like.

Implement DeploymentView similarly to the other view types and uncomment those sections in the examples (with appropriate renaming of vars to be Python style).

One difficulty in implementing this (and possibly for the automated e2e tests) is that deployment views aren't available in the free licence plan.

Additional context

From big_bank.py:

    # DeploymentView liveDeploymentView = views.CreateDeploymentView(internet_banking_system, "LiveDeployment", "An example live deployment scenario for the Internet Banking System.")
    # liveDeploymentView.Environment = "Live"
    # liveDeploymentView.Add(big_bank_data_center)
    # liveDeploymentView.Add(customerMobileDevice)
    # liveDeploymentView.Add(customerComputer)
    # liveDeploymentView.Add(dataReplicationRelationship)
    # liveDeploymentView.PaperSize = PaperSize.A5_Landscape

Resolve overlap between add methods

Some models in the Java client are designed to have a general .add method as well as more specific .add_person, .add_container, etc. methods. In general, it seems like these methods are slightly redundant and thus present some uncertainty in their interface.

I would like to analyze the use-cases and possibly make some simplifications.

Pydantic field validations should not be stricter than the Structurizr server

Problem description

With a new workspace in the Structurizr UI, many fields are optional (may be left blank). The model we have using Pydantic is more strict than this, which means that trying to get the workspace fails on deserialisation.

Code Sample

The stacktrace below is from creating a new workspace with a system context view that has no description. There may well be a host of other fields with the same problem.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\users\xxx\dev\structurizr-python\src\structurizr\api\structurizr_client.py", line 145, in get_workspace
    return Workspace.hydrate(WorkspaceIO.parse_raw(response.text))
  File "pydantic\main.py", line 478, in pydantic.main.BaseModel.parse_raw
  File "pydantic\main.py", line 455, in pydantic.main.BaseModel.parse_obj
  File "pydantic\main.py", line 346, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for WorkspaceIO
views -> systemContextViews -> 0 -> description
  field required (type=value_error.missing)

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                       1.5.4
httpx                        0.15.3
importlib_metadata            1.7.0
ordered-set                     3.1
pip                          20.2.4
pydantic                      1.6.1
python-dotenv                0.14.0
setuptools                   41.2.0
structurizr-python 0.1.0+1.g58d76cb

Support adding animations to static views

Checklist

Is your feature related to a problem? Please describe it.

Following PR #77, the last remaining part of the API needed to complete the big_bank.py example is being able to add animations to static views.

Describe the solution you would like.

Mirroring the Java implementation,, we should add an add_animation() method to StaticView which accepts a variable number of elements, and copy the logic from Java. The serialisation and deserialisation of animation data has already been done, so it's just about being able to add animations programmatically.

    system_landscape_view.add_animation(internet_banking_system, customer, mainframe_banking_system, emailSystem)
    system_landscape_view.add_animation(atm)
    system_landscape_view.add_animation(customerServiceStaff, back_office_staff)

Element.relationships and get_relationships should stay consistent

Problem description

Whilst working with a model and its elements, it is easy to end up in a situation where Element.relationships and the results of the various Element.get_*_relationships methods are not consistent, which may lead to unexpected behaviour.

Code Sample

from structurizr.model import Model
model = Model()
sys1 = model.add_software_system(name="sys1")
sys2 = model.add_software_system(name="sys2")
sys3 = model.add_software_system(name="sys3")
sys4 = model.add_software_system(name="sys4")
sys1.add_relationship(source=sys1, destination=sys2, description="uses")
print("sys1.relationships:      ", sys1.relationships)               # Has the new relationship with no ID
print("sys1.get_relationships():", list(sys1.get_relationships()))   # Empty
print("sys2.relationships:      ", sys2.relationships)               # Empty
print("sys2.get_relationships():", list(sys2.get_relationships()))   # Empty
print()

sys3.uses(sys4, "uses")
print("sys3.relationships:      ", sys3.relationships)               # Has the new relationship with an ID
print("sys3.get_relationships():", list(sys3.get_relationships()))   # Has the new relationship with an ID
print("sys4.relationships:      ", sys4.relationships)               # Empty
print("sys4.get_relationships():", list(sys4.get_relationships()))   # Has the new relationship with an ID

Output:

sys1.relationships:       {Relationship(id=)}
sys1.get_relationships(): []
sys2.relationships:       set()
sys2.get_relationships(): []

sys3.relationships:       {Relationship(id=5)}
sys3.get_relationships(): [Relationship(id=5)]
sys4.relationships:       set()
sys4.get_relationships(): [Relationship(id=5)]

Also need to look at Model.add_relationship() which can construct new relationships but doesn't add them to either the source or destination element.

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                1.5.4
httpx                 0.15.3
ordered-set              3.1
pip                   20.2.3
pydantic               1.6.1
python-dotenv         0.14.0
setuptools            41.2.0
structurizr-python 0+unknown

Add set_automatic_layout() method to View

Checklist

Is your feature related to a problem? Please describe it.

I want to be able to easily switch on automatic layout for a View through an enable_automatic_layout() method.

Describe the solution you would like.

This should mirror the equivalent in the Java implementation, which sets the defaults to be:

  • rank_direction = top_bottom
  • rank_separation = 300
  • node_separation = 600
  • edge_separation = 200
  • vertices = False

Make error message more intuitive if you get a wrong parameter on an element constructor

Problem description

It's easy to get a constructor argument wrong, but at the moment the error you get back isn't very helpful:

>>> Container(name="Container", bar=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
  File "c:\...\structurizr-python\src\structurizr\model\model_item.py", line 86, in __init__
    super().__init__(**kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

This should be more in line with a regular object:

>>> class Foo:
...   def __init__(self):
...     pass
...
>>> Foo(bar=7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'bar'

Code Sample

Create a minimal, complete, verifiable example.

from structurizr.model import Container
Container(name="container", foobar=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\...\structurizr-python\src\structurizr\model\container.py", line 98, in __init__
    super().__init__(**kwargs)
  File "c:\...\structurizr-python\src\structurizr\model\element.py", line 71, in __init__
    super().__init__(**kwargs)
  File "c:\...\structurizr-python\src\structurizr\mixin\model_ref_mixin.py", line 35, in __init__
    super().__init__(**kwargs)
  File "c:\...\structurizr-python\src\structurizr\model\model_item.py", line 86, in __init__
    super().__init__(**kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                              1.5.4
httpx                               0.16.1
importlib_metadata                   1.7.0
ordered-set                            3.1
pip                                 20.2.4
pydantic                             1.7.1
python-dotenv                       0.14.0
setuptools                          41.2.0
structurizr-python 0.1.1+32.g8fa91ab.dirty

Implement DeploymentNode

Checklist

Is your feature related to a problem? Please describe it.

DeploymentNode only currently has a skeletal implementation, and deserialisation does not handle the parent references.

Describe the solution you would like.

Complete the implementation in Model and DeploymentNode.

Support for loading/saving workspace from/to file and string

Checklist

Describe the solution you would like.

To make it simpler to work with workspaces outside the Structurizr API, Workspace should be extended to allow loading and saving from/to files and strings. Mirroring the JSON api, we should support something like:

@classmethod
Workspace.load(cls, file: Union[str, Path]) -> Workspace  # Already supported, handles zipped and unzipped

@classmethod
Workspace.loads(json_string: str) -> Workspace

Workspace.dump(file: Union[str, Path], *, zip: bool = False, indent: int = None) -> None

Workspace.dumps(*, indent: int = None) -> str

We should consider adding a **kwargs which gets passed through to the json method to allow finer-grained control of (de)serialisatoin.

Additional context

Currently to write to file, you have to do something along the lines of:

with open("my-workspace.json", "w") as f:
    f.write(WorkspaceIO.from_orm(workspace).json(indent=2))

which is a bit of a faff.

It'd also be good to show saving and loading to/from file in an example.

Automatic addition of implied relationships

Checklist

Is your feature related to a problem? Please describe it.

When building a model programmatically using Python, a common situation is that you need to create relationships between parent entities that mirror those of children. For example, assume you have system A with container A1 and system B with container B1, then adding a relationship A1 -> B1 usually implies you also have relationships A1 -> B, A -> B and A -> B1 (for example if you are drawing a container view of A then you would want to see the relationship from A1 ->B, or if you are drawing a system context view then you'd want to see A -> B).

Describe the solution you would like.

In Java, this is handled through setting an ImpliedRelationshipStrategy on the Model with three strategies supported out of the box:

We should mirror this structure in Python.

It is also possible to prevent invoking the strategy when adding a single relationship by passing createImpliedRelationships=false on Model.addRelationship(). In Python, this would need to be added in Element.add_relationship() which would then pass through to Model.add_relationship().

Describe alternatives you considered

A simpler solution would be to reproduce the earlier Model.addImplicitRelationships() from Java which you call explicitly after adding a relationship, but it would be better to match the newer Java strategy implementation.

Shouldn't it be possible to add a relationship with source == destination in a dynamic view ?

Checklist

Question

Currently if I try to run the following code, it fails per design:

view = views.create_dynamic_view(element=element1, key="my_dynamic_view", description="description")
view.add(element1, element2, "calls Foo()")
view.add(element2, element2, "calls Bar()")

It fails because A relationship between element2 and element2 does not exist in the model.
In a way, there is an obvious relationship between an element and itself.

Could we implement such behavior ? or is against some logic related to C4 model ?

Allow suppression of "archive" JSON file creation in `ClientSettings`

Checklist

Is your feature related to a problem? Please describe it.

Currently, whenever you fetch a workspace from the Structurizr API, a file is created with the zipped JSON. If you don't desire this behaviour then you can work around it (e.g. by using tempfile.TemporaryDirectory() and putting that in the ClientSettings then cleaning it up afterwards) but this is a bit clumsy and runs the risk of leaving stray files/directories if the process is terminated prematurely.

Describe the solution you would like.

Currently ClientSettings defaults workspace_archive_location to Path.cwd() if unspecified. I propose that instead it should be made optional and if None then StructurizrClient.get_workspace() not generate an archive file, plus by default it is None so archive files do not get created.

Describe alternatives you considered

Alternatively, we could add an explicit boolean flag to ClientSettings to indicate whether archive files should be created, but this seems a little unpythonic.

workspace ID

At the moment both the workspace as well as the Structurizr client require the workspace ID. This seems unnecessary but also currently not like a big deal. The workspace requires the ID in the current OpenAPI schema but the client could retrieve it from the workspace instance.

This design was copied from the Java client and may be improved in future.

StaticView.add_nearest_neighbours should not duplicate relationships

Problem description

If add_nearest_neighbours() is called more than once for the same element (e.g. with different neighbour types) then it can currently duplicate relationships.

Code Sample

Create a minimal, complete, verifiable example.

from structurizr.model import Model, Person, SoftwareSystem
from structurizr.view.static_view import StaticView


class DerivedView(StaticView):
    """Mock class for testing."""
    def add_all_elements(self) -> None:
        """Stub method because base is abstract."""
        pass


def test_add_nearest_neighbours_doesnt_dupe_relationships():
    """Test relationships aren't duplicated if neighbours added more than once."""
    model = Model()
    sys1 = model.add_software_system(name="System 1")
    sys2 = model.add_software_system(name="System 2")
    sys1.uses(sys2)
    view = DerivedView(software_system=sys1, description="")
    view.add_nearest_neighbours(sys1, SoftwareSystem)
    assert len(view.relationship_views) == 1

    # The next line will currently dupe the relationship
    view.add_nearest_neighbours(sys1, Person)
    assert len(view.relationship_views) == 1  # This fails as it's 2

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                              1.5.4
httpx                               0.16.1
importlib_metadata                   1.7.0
ordered-set                            3.1
pip                                 20.2.4
pydantic                             1.7.1
python-dotenv                       0.14.0
setuptools                          41.2.0
structurizr-python 0.2.1+29.gf1b13ef.dirty

Clean up flake8 warnings

Problem description

Running flake8 against the project throws around 100 warnings currently. These should be tidied up so we can enable flake8 as part of the build.

Code Sample

src\structurizr\view\system_context_view.py:65:1: D102 Missing docstring in public method
src\structurizr\view\system_context_view.py:75:1: D102 Missing docstring in public method
src\structurizr\view\system_landscape_view.py:77:1: D102 Missing docstring in public method
src\structurizr\view\terminology.py:31:1: D414 Section has no content
etc.

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                              1.5.4
httpx                               0.16.1
importlib_metadata                   1.7.0
ordered-set                            3.1
pip                                 20.2.4
pydantic                             1.7.1
python-dotenv                       0.14.0
setuptools                          41.2.0
structurizr-python 0.1.1+32.g8fa91ab.dirty

Update source style to match current settings from black

Problem description

From a clean checkout, running make qa reformats a bunch of files, presumably because the version of Black has moved on.

Code Sample

E.g. in test_workspace_io.py:

@pytest.mark.parametrize(
    "example, filename", [("getting_started", "GettingStarted.json"),],
)

becomes

@pytest.mark.parametrize(
    "example, filename",
    [
        ("getting_started", "GettingStarted.json"),
    ],
)

Context

System Information
==================
OS         Windows
OS-release      10
Python       3.7.5

Package Versions
================
depinfo                1.5.4
httpx                 0.15.3
ordered-set              3.1
pip                   20.2.3
pydantic               1.6.1
python-dotenv         0.14.0
setuptools            41.2.0
structurizr-python 0+unknown

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.