pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.49k stars 524 forks source link

Add support for generating state diagram without writing the file to disk #533

Closed Blindfreddy closed 2 years ago

Blindfreddy commented 3 years ago

Is your feature request related to a problem? Please describe. My program has a REST API and returns data in JSON. I'd like to also return the state diagram rendered using transitions get_graph().draw(). Currently, the get_graph().draw() method firstly writes the rendered state diagram to local filesystem, from where I read it, base64 encode it and return it in response to a GET request.

Describe the solution you'd like Render the diagram but don't write it to disk, instead return the stream, or better still directly base64 encode it, thus eliminating the two-step procedure of first writing a file and then reading that file for encoding and returning in the API.

Additional context Add any other context or screenshots about the feature request here.

Current implementation is as follows; it works but note the two steps: first create the file , then read it and base64 encode it. It would be great if the file were not written to the filesystem but directly returned by the draw() method.

    @property
    def state_diagram(self):
        filename = uuid.uuid4().hex
        self._sm.get_graph(show_roi=True).draw(filename, prog='dot')
        with open(filename, mode='rb') as f:
            content = f.read()
        d = {'name' : self.name,
             'filename' : self.name + '_state_diagram.png',
             'mimetype' : 'image/png',
             'encoding' : 'base64',
             'value' : b64encode(content).decode()}
        os.remove(filename)
        return d         
aleneum commented 3 years ago

Hello @Blindfreddy,

I kept you waiting waaaaay too long. The problem is that GraphMachine.get_graph returns a pygraphviz.Agraph object when you use pygraphviz (default backend) and a transitions.Graph when you use graphviz. transitions has no influence on Agraph.draw but from the documentation it seems like just setting path to None does what you require:

Note, if path is a file object returned by a call to os.fdopen(), then the method for discovering the format will not work. In such cases, one should explicitly set the format parameter; otherwise, it will default to 'dot'. If path is None, the result is returned as a Bytes object.

from transitions.extensions import GraphMachine

m = GraphMachine(states=['A', 'B'], transitions=[['go', 'A', 'B']], initial='A')
g = m.get_graph().draw(None, prog='dot', format='jpeg')
print(g)
# >>> b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01 ...

I will have a look to implement a similar behaviour for the graphviz backend.

Blindfreddy commented 2 years ago

Hi @aleneum Thanks for the response, sounds like a plan. In the meantime I implemented a workaround which writes the file locally, reads it and then deletes it. Not pretty but works.

        filename = uuid4().hex + '.png'
        self._sm.get_graph(show_roi=True).draw(filename, prog='dot')
        with open(filename, mode='rb') as f:
            content = f.read()
            ...
        os.remove(filename)
aleneum commented 2 years ago

Hi,

in 9370834, I implemented the aforementioned behaviour. Let me know if this works for you. If you are happy with your workaround, you can also go ahead and just close this issue.

Blindfreddy commented 2 years ago

I'm afraid I don't know how to install the abovementioned commit using pip prior to release 0.8.9. Tried git+git://github.com/pytransitions/transitions/commit/9370834b412d7d0efecae520bf672b4931fb1a38 to no avail, any help with getting it installed would be much appreciated. Otherwise wait until 0.8.9 is released.

aleneum commented 2 years ago

you could just install transitions from master I guess:

pip install git+https://github.com/pytransitions/transitions
aleneum commented 2 years ago

0.8.9 has been released which includes the requested feature. I will close this issue. Feel free to comment if you consider this issue not resolved. I will reopen the issue if required.