
Python and Flask Are Powerful - nichochar
http://jeffknupp.com/blog/2014/01/18/python-and-flask-are-ridiculously-powerful/
======
freyr
Python and Flask are great, but the secret ingredient here seems to be Stripe.
His goal was payment processing, and Stripe made everything else nearly
trivial.

~~~
lurchpop
Agreed. This is freakin' magic:

    
    
        try:
            charge = stripe.Charge.create(
                    amount=int(product.price * 100),
                    currency='usd',
                    card=stripe_token,
                    description=email)
        except stripe.CardError, e:
            return """..err.."""
        print charge

~~~
jknupp
I could not agree more. Stripe = insanely awesome payment processor. With all
the heavy lifting they do,

I'm now unsure what something like Gumroad's value proposition is, considering
I'm basically at feature parity with them (aside from PDF stamping, which I
don't do anyway).

~~~
ericd
There are more people who don't know how to code than do.

------
smoyer
Using UUIDs as the endpoint for an Internet resource is a good idea since
they're (by nature) unique. When using them for proof of payment, you have to
be a little more careful - ideally you want a very sparce matrix with a flat
distribution of "consumed" UUIDs within the 128-bit space. Choosing the wrong
UUID version/variant will clump your "consumed" UUIDs together, so for the
author's purposes, he'd want to use version 4 which has 122 bits of randomized
data as shown in his code:

purchase = Purchase(uuid=str(uuid.uuid4()),

Other than making sure you're careful with the customer's credit card data
(not so much of an issue using Stripe), this was probably the portion of the
code where a mistake could have decreased the security of the system.

[http://en.wikipedia.org/wiki/Universally_unique_identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier)

~~~
sheng
I'm not familiar with SQLAlchemy but my overall SQL sense is tingling here:

    
    
      purchase.downloads_left -= 1
    

Is this the preferred method to update a field? I'm asking because i'm
wondering if sqlalchemy will translate this to an sql query similar to:

    
    
      UPDATE purchase SET downloads_left = downloads_left - 1;
    

Because otherwise this might be dangerous.

~~~
zzzeek
not in that case. if the code wanted to do this decrement on the SQL side it
would have to be:

    
    
        purchase.downloads_left = Purchase.downloads_left - 1
    

which would emit on the next flush. in this code specifically, using that
approach it would have to call session.flush() and then re-query for that
value since it wants to check what the database came up with. So in that sense
it would be better just to emit an explicit UPDATE..RETURNING, which is easy
to do with SQLA; this is an example of how "dropping down" a level of
abstraction is a critical feature with SQL abstraction tools.

however, this is only one way to do it, which is the so-called pessimistic
approach. An optimistic approach would just ensure that the transaction
isolation is in repeatable read, so that the flush (occurs within the commit()
here) would just fail in the very unlikely case a single user is submitting
twice. SQLAlchemy also offers a "version counter" feature that can accomplish
the same task if RR isn't an option. Both of these are configuration-level
features that would allow the code to remain unchanged.

The "session.add(purchase)" is also unnecessary in that code sample, and the
code also has a bug in that it does not commit the transaction when
downloads_left reaches zero, so the number can never actually reach zero in
the database.

~~~
jknupp
Both of these have been fixed. Thanks for pointing out the off by one error
and redundant session.add()

------
adnam
Also, _django_ and Flask are extremely powerful. Django gives you an automatic
admin interface and ORM, and Flask makes it easy to create a simple API to
your data.

    
    
      from django.conf import settings as django_settings
      from myapp import settings
      myapp_settings = return dict([(s, getattr(settings, s)) for s in dir(settings) if s==s.upper()])
      django_settings.configure(**myapp_settings)
      from myapp.models import User
    
      app = Flask(__name__)
      api = restful.Api(app)
      
      class UserResource(restful.Resource):
          def get(self):
              return {"result": User.objects.all()}, 200
      api.add_resource(UserResource, '/')
    
      if __name__ == '__main__':
          app.run(debug=myapp_settings['DEBUG'])
    

There's just a little trick to get your API to convert django models to Json:

    
    
        from flask.json import JSONEncoder as FlaskJSONEncoder
        from django.db.models import Model
        from django.db.models.base import ModelBase
        from django.db.models.query import QuerySet, ValuesQuerySet
        from django.db.models.fields.related import ManyToManyField
        from datetime import datetime
    
        def model_to_dict(instance):
            """Same as django.forms.models.model_to_dict, but returns
            everything, including non-editable fields"""
            opts, data = instance._meta, {}
            for f in opts.concrete_fields + opts.many_to_many:
                if isinstance(f, ManyToManyField):
                    data[f.name] = []
                    if instance.pk is not None:
                        data[f.name] = list(f.value_from_object(instance).values_list('pk', flat=True))
                else: data[f.name] = f.value_from_object(instance)
            return data
    
        class ApiJSONEncoder(FlaskJSONEncoder):
            def default(self, obj):
                if isinstance(obj, (Model, ModelBase)):
                    return model_to_dict(obj)
                if isinstance(obj, (QuerySet, ValuesQuerySet)):
                    return [model_to_dict(m) for m in obj]
                elif isinstance(obj, datetime):
                    return obj.isoformat("T")
                return FlaskJSONEncoder.default(self, obj)

~~~
nichochar
ha, this is crazy useful, thank you

~~~
adnam
You're welcome ^_^

------
zrail
This is a great illustration of the power that APIs like Stripe's give a
developer. That said, there are a few things that would make this integration
more bulletproof. In particular, interactions with external services like
Stripe and email should be handled in a background thread. That way you don't
have to worry about transient API or SMTP server failures giving your
customers a bad experience. The best bet for Python is Celery, iirc.

<shameless plug>

I wrote a book about combining Stripe and Rails.

[https://www.petekeen.net/mastering-modern-
payments](https://www.petekeen.net/mastering-modern-payments)

</plug>

~~~
hcarvalhoalves
Celery has too many moving parts though. For the simple use case of just off-
loading Stripe processing to a queue the RQ module can be better.

[http://python-rq.org](http://python-rq.org)

~~~
shavenwarthog2
Agreed. I got my first job running correctly with Rq in fifteen minutes, and
was able to go on to other things. Recommended!

------
abritishguy
I had a very similar thing when I wanted to buy an SSL certificate, all the
providers were needlessly complex so I built my own wrapper around their
reseller API using stripe:

<shameless plug - trying to pay my way through uni ;)>

[https://www.volcanicpixels.com/ssl/buy](https://www.volcanicpixels.com/ssl/buy)

~~~
glazskunrukitis
That's basically on of the main reasons why I built getssl.me. I was fed up
with the over-complicated process of buying a SSL certificate.

Your variation seems even simpler and more straightforward - congrats on
launching and all the best for your project!

------
workhere-io
Considering how easy templating is with Flask, I don't understand who someone
would write HTML directly in the controller the way the author did. I agree
that Flask is great, though.

~~~
adnam
It was just an example.

~~~
workhere-io
An example that beginners would be likely to be inspired by. It wouldn't have
been much harder to write a single line of

render_template('something.html')

and then write the HTML for the template in a separate code section.

~~~
emmelaich
An example should demonstrate what it is meant do demonstrate, nothing more.

Anything else is a distraction.

Using render_template would have left us wondering whether there was anything
relevant to the task in render_template, or at best interrupting the reader's
flow.

------
windexh8er
I'm not a professional programmer by any means but I use Python daily and
Flask is my goto framework. I wrote an automated commit system leveraging the
API in Palo Alto Networks platform in a day. Auth, sessions, pagination,
templates, CSRF, safe data, scheduling, etc. All easy to use, no bloat.

I just enjoy Python in general as I can get things done so quickly. Especially
with a nice environment setup using phone and pip. On top of that PyCharm just
works.

I've tried a number of other languages over the years, but could never get to
the level of productivity as I've always gotten with Python. And now I'm going
to have to buy the OPs book...

------
sputnikus
And now somebody wants to get that book or die trying
[https://twitter.com/jeffknupp/status/424925349525200896](https://twitter.com/jeffknupp/status/424925349525200896)

------
mox1
I would agree with with the author in gnenral. Django is very powerful, but it
can be painful, simply for the mental capacity required to get up and running
with it. Sure after you spend a few (at least) hours wrapping your head around
everything it becomes pretty easy to use...but still a high barrier to entry.

This is part of the reason I'm becoming more and more of a fan of web.py and
peewee
([https://github.com/coleifer/peewee](https://github.com/coleifer/peewee)).
They are simple but still powerful enough for the majority of use cases.

------
f4stjack
Just like Ruby and Sinatra. Although Rails is more popular, I found Sinatra's
barebones approach a little bit more approachable. Combined with an ORM
solution it is very possible to create a lightweight web app. Then again I do
not know how it performs under stress.

------
jwmerrill
The call to Stripe.charge.create is blocking, in the sense that the server
can't start processing a new request while it's busy waiting for a response
from stripe in an existing request, right?

I think this kind of use case is exactly what has people so excited about
node, since, in a single thread, it can go on processing new requests while
it's waiting to hear back from, e.g., stripe (or the database, or the disk, or
any other kind of IO).

May your book go on to have so many concurrent sales that this becomes an
issue!

~~~
workhere-io
_The call to Stripe.charge.create is blocking, in the sense that the server
can 't start processing a new request while it's busy waiting for a response
from stripe_

Would this even be an issue if you're using e.g. Apache (multiple threads)
with mod_wsgi?

------
coldcode
Good thing he doesn't need to get a PCI audit. I wish we could get away with
only a CC number and expiry date and email.

~~~
jknupp
I never actually store (or see) any CC information. All of that is handled on
Stripe's end.

------
gingerlime
What about paypal as a payment option however? Isn't this still a pretty major
PITA to support?

~~~
abritishguy
Not if the target market is devs, we hate paypal.

------
sighol
That looks really simple and all, but isn't the concept with Stripe a litte
unsecure? I really prefer to to use a third-party solution when I use my
credit card online. Using this method I can't know for sure that the developer
isn't saving my credit card info.

Wouldn't it be better to use Paypal or some well known third party for
something as important as payment?

------
ansgri
Does really work, 20 s or less. The download seems to be slow though.

------
steffan
I'm very happy with Stripe. I would almost call their API beautiful. It's
simple and well-designed, and has been very easy to integrate using stripe.js
and stripe-scala.

------
squigs25
Cool - pretty elegant solution to a common problem. Interestingly, the
solution was "enabled" by recently available technology packages, like stripe
(and flask).

------
rtpg
slightly offtopic but I find charging to have the 'right' to have a file in
two different file formats to be slightly insulting. $5 for a binary flag?

~~~
dhon_
I think you're paying the $5 for getting the Python 2 & 3 versions of the book
more so than the different file formats. After all, you could convert formats
fairly easily.

------
iurisilvio
Point where we can give feedback about your book. A github repo is perfect for
that.

I found some issues and didn't reported because it is too dificult.

------
notastartup
is there a python version of SaaS app using Stripe?
[https://github.com/RailsApps/rails-stripe-membership-
saas](https://github.com/RailsApps/rails-stripe-membership-saas)

------
stefantalpalaru
Apparently not powerful enough to convince the programmer that mixing Python
and HTML is a bad idea.

------
ossdev1
I don't know is this a forum for Python promotion?

~~~
rmrfrmrf
I'm hoping the HN crew looks into this -- 95% of the comments are selling
something, and the post itself is useless garbage. There's no reason that this
should be the top post.

~~~
ossdev1
Exactly. Besides they are downvoting all critical comments, so nobody can
disagree with mainstream's publicity.

