storage = Storage()
cleanup = CleanupStoragePathStep(storage).call
package = BifPackager(s3_storage=storage, cleanup_storage_path=cleanup).package
result = package.run(params=movie_options)
if result.is_success:
return HTTPResponse(status=200)
elif result.failed_because('download_error'):
return HTTPResponse(
status=500,
data={
'error': 'download_error',
'message': f'Host response: {result.ctx.downloaded_movie}',
}
)
elif result.failed_because('cleanup_error'):
...
In the case of Failure
, the actual execution context is available in the result .
Why do we need this kind of separation?
Let's imagine the situation we want to use this story in a few places. For example, Django Web application build with forms, Django admin actions and REST framework API endpoint.
In each case interpretation of the same failure will be different.
In the future if web interface should support internationalization, Django admin and API endpoint should not know anything about it.
And if instead of building an actual response representation inside the story, we build it as close to the place where it actually used - we provide the necessary separation of concerns.
TL;DR; Build message outside of the story.
Hope that helps.
Regards,
Artem.
Привет!
stories
лучше всего использовать вместе с dependencies
.
Рассмотрим как пример код из tutorials. У нас есть:
Для DRF всё тоже готово. Можно взять, например, ModelViewSet. У него есть методы create, update, destroy так же как у form view есть form_valid.
Если есть желание, можешь запилить api слой для tutorials. Готов менторить.
Надеюсь было полезно.
def foo(a, b, *, c=1, d=2)
синтаксиса.
accepted_data = {
key: value for key, value in ctx.__dict__[
'_Context__ns'
].items()
}
@proofit404 Добрый день! возможно ли (чтобы убрать лишний step вызова реализации) вместо:
>>> def find_price(self, ctx):
... ctx.price = self.impl.find_price(ctx.price_id)
... return Success()
>>> def __init__(self, impl):
... self.impl = impl
Использовать:
>>> class Price:
...
... @story
... @arguments("category", "price_id")
... def find_price(I):
...
... I...
... I...
>>> class Subscription(MethodDefinitions):
...
... @story
... @arguments("category_id", "price_id", "profile_id")
... def buy(I):
...
... I.find_category
... I.impl.find_price
...
... def __init__(self, impl):
... self.impl = impl
Subscription(impl=Price()).buy
print(ctx)
. Но в коде будет грязь.
@dataclass
class TokenUseCase:
@story
@arguments('user')
def obtain_token(I):
I.require_user_active
I.create_token
I.return_token
def require_user_active(self, ctx):
if ctx.user.is_active:
return Success()
return Failure()
def create_token(self, ctx):
ctx.token = self.create_user_token(ctx.user)
return Success()
def return_token(self, ctx):
return Result({'token': ctx.token})
# deps
create_user_token: Callable
@proofit404 Добрый вечер! Например у меня есть TokenUseCase, который я хочу использовать напрямую для получения токена.
result = TokenUseCase(create_user_token=create_user_token).obtain_token.run(user=user)
token = result.value['token']
Но также я хочу переиспользовать TokenUseCase.obtain_token как sub-story.
Но так сделать не получается, т.к. obtain_token возвращает Result и прекращает выполнение OtherUseCase.outer_story
obtain_token = TokenUseCase(create_user_token=create_user_token).obtain_token
OtherUseCase(find_token=obtain_token).outer_story.run()
Как правильно организовать код в таком случае? Заранее спасибо!
Привет. Сразу скажу что красивого решения я пока не нашёл.
Я обычно такое поведение выношу в отдельную story, т.е. Result
возвращаю на верхнем уровне вложенности.
@dataclass
class TokenUseCase:
@story
@arguments('user')
def obtain_user_token(I):
I.obtain_token
I.return_token
@story
@arguments('user')
def obtain_token(I):
I.require_user_active
I.create_token
Таким образом мы можем вызвать obtain_user_token
как самостоятельную сторю, а obtain_token
как дочернюю сторю для какой-то более большой.