doctype html
html
head
meta(charset="utf-8")
meta(name="description" content="Introducing dry-python")
meta(name="author" content="Artem Malyshev")
meta(name="apple-mobile-web-app-capable" content="yes")
meta(name="apple-mobile-web-app-status-bar-style" content="black-translucent")
meta(name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")
title Introducing dry-python
body
.reveal
.slides
section
h2 Introducing
h1
a(href="https://dry-python.org/").
dry-python
h4 Artem Malyshev
section
h2 BIO
ul
li Co-Founder at #[a(href="https://drylabs.io/") drylabs.io]
li #[a(href="https://dry-python.org/") dry-python.org]
li Django Channels 1.0
li 5 years of experience in Python
section
h2 Code is...
p.fragment
b hard
p.fragment
b and frustrating
p.fragment Let's consider we're developing subscription button for a web service
section(data-background-image=require("./pics/startup.jpg").default data-background-size="contain")
h2(style="color: white; background-color: black") Startup
br
br
br
br
br
br
br
br
section
h2 Micro framework
ol
li.fragment Long handlers
li.fragment Lots of "if" statements
section
h2 Long handlers
pre
code.python.
85 @app.route('/subscriptions/')
86 def buy_subscription(page):
...
121 if props[-1].endswith('$'):
122 -#{">"} props[-1] = props[-1][:-1]
123
pre.fragment
code.python.
Traceback (most recent call last):
File "views.py", line 1027, in buy_subscription
ZeroDivisionError: division by zero
section(data-background-image=require("./pics/enterprise.jpg").default data-background-size="contain")
h2(style="color: white; background-color: black") Enterprise
br
br
br
br
br
br
br
br
section
h2 Big framework
ol
li.fragment You need method flowchart
li.fragment Zig-zag in the traceback
li.fragment Framework internals leak
section
h2 Implicit API
pre
code.python.
class SubscriptionViewSet(viewsets.ModelViewSet):
queryset = Subscription.objects.all()
serializer_class = SubscriptionSerializer
permission_classes = (CanSubscribe,)
filter_class = SubscriptionFilter
ol
li.fragment What exactly does this class do?
li.fragment How to use it?
section(data-background-image=require("./pics/method-flowchart.png").default data-background-size="contain")
br
section
h2 Framework internals leak
pre
code.python.
class SubscriptionSerializer(Serializer):
category_id = IntegerField()
price_id = IntegerField()
pre.fragment
code.python.
def recreate_nested_writable_fields(self, instance):
for field, values in self.writable_fields_to_recreate():
related_manager = getattr(instance, field)
related_manager.all().delete()
for data in values:
obj = related_manager.model.objects.create(
to=instance, **data)
related_manager.add(obj)
section
h2 As a result code is...
ol
li.fragment Fragile
li.fragment Hard to reason about
li.fragment Time-consuming
section
img(src=require("./pics/crazy-telephone-wires.png").default)
section
blockquote
p If your code is crap, stickies on the wall won't help.
a(href="https://twitter.com/henrikkniberg").
@HenrikKniberg
img(src=require("./pics/stickers-on-the-wall.jpg").default)
section
h2 Service layer
p Defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation.
p
b by Randy Stafford
section
h2 Business objects
pre
code.python.
def buy_subscription(category_id, price_id, user):
category = find_category(category_id)
price = find_price(price_id)
profile = find_profile(user)
if profile.balance #{"<"} price.cost:
raise ValueError
decrease_balance(profile, price.cost)
save_profile(profile)
expires = calculate_period(price.period)
subscription = create_subscription(
profile, category, expires)
notification = send_notification(
'subscription', profile, category.name)
section
h2 Business object problems
ol
li.fragment Mixed state, implementation and specification
li.fragment Growth problem
li.fragment Top-down architecture
section
img(src="https://raw.githubusercontent.com/dry-python/brand/master/logo/dry-python.png").plain
p A set of libraries for pluggable business logic components.
p Answers how we decompose and organize business logic.
section
img(src="https://raw.githubusercontent.com/dry-python/brand/master/logo/stories.png").plain
p Define a user story in the business transaction DSL.
p Separate state, implementation and specification.
section
h2 DSL
pre
code.python.
from stories import story, arguments
class Subscription:
@story
@arguments('category_id', 'price_id')
def buy(I):
I.find_category
I.find_price
I.find_profile
I.check_balance
I.persist_payment
I.persist_subscription
I.send_subscription_notification
section
h2 Context
pre
code.python.
(Pdb) ctx
Subscription.buy:
find_category
check_price
check_purchase (PromoCode.validate)
find_code (skipped)
check_balance
find_profile
Context:
category_id = 1318 # Story argument
user = #{"<"}User: 3292#{">"} # Story argument
category = #{"<"}Category: 1318#{">"}
# Set by Subscription.find_category
section(data-background-image=require("./pics/debug-toolbar.png").default data-background-size="contain")
h2 DEBUG TOOLBAR
br
br
br
br
br
br
br
br
br
br
section(data-background-image=require("./pics/pytest.png").default data-background-size="contain")
h2(style="color: white") py.test
section(data-background-image=require("./pics/sentry.png").default data-background-size="contain")
h2 Sentry
section
h2 Usage
ol
li.fragment Story decorator build an execution plan
pre
code.python.
class Subscription:
@story
def buy(I):
I.find_category
li.fragment Execute object methods according to plan
pre
code.python.
def find_category(self, ctx):
category = Category.objects.get(
pk=ctx.category_id)
return Success(category=category)
li.fragment We call the story method
pre
code.python.
subs = Subscription()
subs.buy(category_id=1, price_id=1)
section
h2 Failures
ol
li.fragment Define a number of reasons
pre
code.python.
@Subscription.buy.failures
class Errors(Enum):
low_balance = auto()
li.fragment Failure will stop the execution of the whole story
pre
code.python.
def check_balance(self, ctx):
if ctx.profile.balance #{"<"} ctx.price.cost:
return Failure(Errors.low_balance)
else:
return Success()
li.fragment We check failure reason
pre
code.python.
result = Subscription().buy.run(category_id=2)
assert result.failed_because(Errors.low_balance)
section
h2 Contract
ol
li.fragment Define a number of variable validators
pre
code.python.
from pydantic import BaseModel
@Subscription.buy.contract
class Context(BaseModel):
user: User
category_id: int
category: Optional[Category]
li.fragment Return variables from step
pre
code.python.
def find_category(self, ctx: "Context"):
category = get_category(
ctx.category_id)
return Success(category=category)
section
h2 Substories
ol
li.fragment Steps can be stories as well
pre
code.python.
class Subscription:
@story
def buy(I):
I.calculate_discount
I.check_balance
@story
def calculate_discount(I):
I.find_promo_code
I.check_code_expiration
li.fragment Each step can stop the execution of current substory
pre
code.python.
def check_code_expiration(self, ctx):
if ctx.promo_code.is_expired():
return Skip()
else:
return Success()
section
img(src="https://raw.githubusercontent.com/dry-python/brand/master/logo/dependencies.png").plain
p Provide composition instead of inheritance.
p Solves top-down approach problem.
section
h2 Delegate responsibility
pre
code.python.
class Subscription:
def find_category(self, ctx):
category = self.load_category(ctx.category_id)
return Success(category=category)
def find_price(self, ctx):
price = self.load_price(ctx.price_id)
return Success(price=price)
def __init__(self, load_category, load_price):
self.load_category = load_category
self.load_price = load_price
section
h2 Injection
pre
code.python.
from dependencies import Injector, Package
app = Package('app')
class BuySubscription(Injector):
buy_subscription = app.services.Subscription.buy
load_category = app.repositories.load_category
load_price = app.repositories.load_price
load_profile = app.repositories.load_profile
BuySubscription.buy_subscription(category_id=1, price_id=1)
section
h2 Django views
pre
code.python.
from dependencies import operation
from dependencies.contrib.django import view
from django.http import HttpResponse, HttpResponseRedirect
@view
class BuySubscriptionView(BuySubscription):
@operation
def post(buy_subscription, category_id, price_id):
result = buy_subscription.run(category_id, price_id)
if result.is_success:
return HttpResponseRedirect(to=result.value)
elif result.failed_on('check_balance'):
return HttpResponse('#{"<"}h1#{">"}Not enough money#{"<"}/h1#{">"}')
section
h2 Flask views
pre
code.python.
from dependencies import operation
from dependencies.contrib.flask import method_view
from flask import redirect
@method_view
class BuySubscriptionView(BuySubscription):
@operation
def post(buy_subscription, category_id, price_id):
result = buy_subscription.run(category_id, price_id)
if result.is_success:
return redirect(result.value)
elif result.failed_on('check_balance'):
return '#{"<"}h1#{">"}Not enough money#{"<"}/h1#{">"}'
section
h2 Celery Tasks
pre
code.python.
from dependencies import operation
from dependencies.contrib.celery import task
@task
class PutMoneyTask(PutMoney):
@operation
def run(put_money, user, amount, task):
result = put_money.run(user, amount)
if result.is_failure:
task.on_failure(result.ctx.transaction_id)
section
h2 Plans
ol
li Delegates
li Rollbacks
li asyncio support
li pyramid support
li typing advantages
li linters integration
li language server
section
h2 Try it!
pre
code.
$ pip install stories
pre
code.
$ pip install dependencies
section
h2 Get in touch
ul
li
a(href="https://dry-python.org/") dry-python.org
li
a(href="https://twitter.com/dry_py") twitter.com/dry_py
li
a(href="https://github.com/dry-python") github.com/dry-python
li
a(href="https://gitter.im/dry-python") gitter.im/dry-python