And one more question. In languages where we have pattern matching out of the box, we can control how left side is built. What can we do here to limit different user attempts to write something like:
when(next(Instance(x, 4, force=True)) >> ...?
Sorry, did not see it on my mobile. I think we should not restrict this. The only thing we care is a reference to the value. If user care about code readability, he/she will not write function calls inside
when. If the user does not care, what is the point to use dsl in the first place?
==against value is not enough, user can pass a predicate function.
@lig After the first public release I think we should try to build a class based DSL.
I write my thoughts on this process in the FAQ https://stories.readthedocs.io/en/latest/faq.html#what-is-the-best-way-to-prototype-my-own-dsl-look-and-feel
Can you start slowly rewrite examples module?
Maybe PR will work for us?
Still unclear to me how to handle Skip
Just like you would do in plain Python. Using conditionals.
The whole point of
Do is that you don’t have to build your own DSL. You’ll be able to use plain Python (or Ruby) code instead of some fancy DSL which gets pretty complicated
After over two years of use & contribution to the Ruby library, we dropped it and switched to plain monads. The complex DSL provides little to no value when we have a special syntax which makes it easier to work with monads.
The worst thing is: it’s hard to wrap multiple steps into a database transaction. My colleague managed to write an
around step, but it was pretty complicated
save_pointsinto this workflow. real world example: I have a complicated task resolution workflow in my bot (here's some details about this workflow: https://www.draw.io/?lightbox=1&highlight=0000ff&nav=1&title=billing.xml#Uhttps%3A%2F%2Fraw.githubusercontent.com%2Fwemake-services%2Fmeta%2Fmaster%2Fprocesses%2Fdevelopment.xml) so, sometimes I need savepoints inside my transactions, because things fail sometimes. and I still need some data to be saved. how can this be achieved right now?
Oh I just realized something
Still unclear to me how to handle Skip
Skip essentially means that the function performed something and returned the input. Practically, it’s just a function which accepts an
x and returns
Success(x). That’s how you handle
There’s probably no need to add any extra abstractions. Moreover, PEP 20 advises against that
storiesDSL be so limited it force the user to explain his/her thoughts in a straightforward way.
I plan to provide Rollback result type to the return types. This is how I see it:
Rollback()result the whole story will be "played" in the reverse order using
Rollback.Current()will rollback only this substory. Caller story will be continued.
What do you think?
Skipworks on substories, not current step. More info here: https://stories.readthedocs.io/en/latest/execution.html#skip
I take a quick overview of the opus library.
It really looks like the dry-transaction library for the Ruby language.
As we discussed earlier, the closest alternative for Python will be a DSL based on class attributes.
I still trying to wrap my head around this idea.
I notice a few things we definitely should do in stories:
We already implement the possibility to skip steps, but I dislike
:if statement next to the step definition. I harm readability in my opinion.
The same goes to
Also, I think retries is out of the scope of the
stories library and should be implemented inside story steps by the final user.
Thanks for the sharing!
HI! Thanks for giving
stories a try!
Failure Protocols is a complete but unreleased feature.
As you can see the only left thing to do is documentation.
It's in its final form and will not change in the near future.
But to use it, you should install from the master.
I hope this example will shed some light on it.
from enum import Enum, auto from stories import Failure, Result, Success, arguments, story # Define parent story class YoutubeDownload: @story @arguments("url", "directory") def download(I): I.fetch_file I.decode_media I.print_result def decode_media(self, ctx): try: ... except MP4FormatError: return Failure(YoutubeErrors.invalid_media) else: return Success(...) def print_result(I): return Result(...) def __init__(self, fetch_file): self.fetch_file = fetch_file # Define parent story failure protocol. @YoutubeDownload.download.failures class YoutubeErrors(Enum): invalid_media = auto() # Define sub-story. class FetchVideo: @story @arguments("url", "directory") def fetch(I): I.create_temp_dir I.open_stream I.download_locally I.move_to_target_dir def create_temp_file(self, ctx): try: ... except IOError: return Failure(FetchErrors.no_space_left) else: return Success(...) def open_stream(self, ctx): try: ... except HTTP401: return Failure(FetchErrors.unauthorized) else: return Success(...) def download_locally(self, ctx): try: ... except IOError: return Failure(FetchErrors.no_space_left) else: return Success(...) def move_to_target_dir(self, ctx): try: ... except IOError: return Failure(FetchErrors.no_space_left) else: return Success(...) # Define sub-story failure protocol. @FetchVideo.fetch.failures class FetchErrors(Enum): no_space_left = auto() unauthorized = auto() # Instantiate parent and sub stories. fetch_video = FetchVideo().fetch youtube_download = YoutubeDownload(fetch_video).download # Run the story. # # Failures were merged from the story hierarchy, so we can check them # in one place. Use `youtube_download.failures` instead of # `YoutubeErrors` or `FetchErrors`. result = youtube_download.run(url="...", directory="...") if result.is_success: ... elif result.failed_because(youtube_download.failures.no_space_left): ... elif result.failed_because(youtube_download.failures.unauthorized): ... elif result.failed_because(youtube_download.failures.invalid_media): ...
Feel free to ask any questions about code snippet above.