Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Python and Flask Are Powerful (jeffknupp.com)
260 points by nichochar on Jan 19, 2014 | hide | past | favorite | 75 comments


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.


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


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).


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


Great article. Last summer I put together a concept app for a micro-coding marketplace using Python and Flask. Stripe made taking payments insanely simple.


Global availability.

After a few years, Stripe is still only available in a handful of countries. If you live in a country that is not only not US (tier 1 for pretty much everything), but not even the handful of high-priority countries like Canada or UK, your options are greatly reduced. For me, for example, it's PayPal or FastSpring - I would love to use Stripe, but I simply can't.


Ditto. Stripe rocks! Simple to integrate and get it up and running in few mins.


Which part of this is 'magic'? Serious question. That seems like standard python api client + standard payment processor api.


For those of us who've used Authorize.net in the past, a simple API client is essentially magic.


I agree. Maybe I'm getting to be one of those old unix dudes or something, but I was doing something that was essentially exactly the same in 2000 using Lotusscript on Domino 4.6.

You gather params, post it somewhere, check out the response, and display it. It gets fancier with age, but it's essentially the same thing.


Take a look at the paypal api.

Also the stripe_token is pretty magic, it lets you make charges to customer credit cards without storing the card details or having to take them to an external payment page.


Have you actually ever integrated with a payment processor before? I have. I agree that this is magical.


I use Stripe on my site https://gauthify.com (first I used a while back, sticky to change)

I use Braintree at my current job.

I used to work for Balanced.

I know payment processors. Its just times have changed and this kind of thing is now quite standard. But 'magical' is of opinion, I was just inquiring what he found magical.


late to the party, but "magic" is the explanation for a dramatic difference between the expected difficulty and the actual difficulty. For example reading my mind to determine my choice of card should involve neurosurgery, EEG and MRI scans and training corpuses - however a 15 year old with a deck of cards can do it - which clearly is magic.


That's the part I didn't get: he says he wrote this to replace three different processors. If Stripe is a payment processor, what exactly did he replace?


Sorry, the terminology is confusing. What I replaced are the digital goods checkout services (that use processors like Stripe in the backend). They help you manage and sell digital goods, by, for example, generating a unique token for each purchase so that every customer has a different download URL. Clearly, as seen here, they don't add much value.


Gumroad charges 5% + 25¢ per transaction.

Compared with Stripe's 2.9% + 30¢, it's expensive, but Gumroad appears to do two things that Stripe doesn't:

- Work out whether and how much sales tax to charge (which I guess should be easy if you operate in only one state)

- Absorb chargeback fees

This second one makes the 5% seem more reasonable.


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


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.


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.


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


ORMs are only trusted if the SQL they create seems sane when compared to how you'd write the SQL manually. After six years with Hibernate and SQLAlchemy (via TurboGears), I still turn on SQL statement logging to check what's being sent for each transaction.


I am pretty sure SQLAlchemy will translate that into:

    UPDATE purchase SET downloads_left=%(downloads_left)s WHERE purchase.id = %(purchases_id)s;
By the way, the posted code has an off-by-one error, as it should do the checking first before the minus operation. Also, the line `db.session.add(purchase)` is redundant.

EDIT: remove bad sample


Even if it did translate to that, what happens when downloads_left is 1 and two updates were to run concurrently?

Unless the data isn't that important, it's better to use an RDBMS with SSI[1] support, and retry or bail out if there's a concurrent write.

http://www.postgresql.org/docs/current/static/mvcc-intro.htm...


yes, it should


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)



or you can use django-rest-framework


ha, this is crazy useful, thank you


You're welcome ^_^


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

</plug>


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


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


Ah, didn't know about RQ. Thanks!


I disagree. I would still have to wait for the API to return before I can proceed with the transaction and display the download page, so doing it synchronously makes more sense. And the overhead introducing Celery or python-rq would add versus the response time difference doing asynchronous email (which I'm using GMail for anyway, so I'm not too worried about it going down) makes the synchronous approach even more attractive.


Sure. But what happens if Stripe's charge API takes 30 seconds and then Gmail takes a few seconds? The users browser times out and they're left looking at a 500 error. With asynchronous processing you get a lot of flexibility with regards to the failure modes. (This is not idle talk. We experienced this at my day job which is specifically what drove me to write my book.)

For email you should probably not use Gmail. They have weird limits and you can easily run afoul of them. A better route is something like Mailgun or Mandrill. They're both free for a large number of outgoing sends and have nice logging and tracking options.


> For email you should probably not use Gmail.

I totally agree here. You're also forced to store a password (even if it's app-specific) in plain text, and the tracking/delivery features are non-existent.

The Mandrill API is pretty rad (I'm using it in my Go app) and the tracking, ability to re-send an email from the last 24 hours (on the free plan) and the server-side templates are all very useful.

    try:
    mandrill_client = mandrill.Mandrill('YOUR_API_KEY')
    message = {
                'subject': "Hello there.",
                'to': [{'email': 'recipient.email@example.com',
             'name': 'Recipient Name',
             'type': 'to'}],
             'from_name': "Matt",
             'from_email': "matt@example.com",
              }
    result = mandrill_client.messages.send(message=message, async=True) // It'll fire it off without waiting, and you can either set up a web-hook *or* manually re-send if it fails via the dashboard if you don't do massive volumes

    except mandrill.Error, e:
    # Mandrill errors are thrown as exceptions
    print 'A mandrill error occurred: %s - %s' % (e.__class__, e)
    # A mandrill error occurred: <class 'mandrill.UnknownSubaccountError'> - No subaccount exists with the id 'customer-123'    
    raise
Pretty simple stuff. Add in some MergeVars (|NAME|, |PURCHASE_ID|) and server-side templates and you can pass whatever is needed to the API without having to keep "content" in your code or pull in an external template (i.e. Jinja).


I agree for email but transactions are very different, I want to know whether my transaction has been successful there and then and get access to what I have paid for. I don't want to be moved to a page saying that my transaction will be processed shortly. Also there would have to be much more than 30 seconds of lag before the connection would timeout.


First, zrail's book is definitely related. :) It's great luck and opportunity stripe got mentioned here.

I have a question on HN etiquette re: plugging decorum. I seen a few instances of this in passing, I thought it was cool.

If it was in this instance, for example: What they did was drop the bait. "I wrote a book about combining Stripe and Rails". Then a reply comment, 99% of the time, will ask for them to dish out the link.

If zrail simply mentioned his book on rails and stripe, my curiosity would have compelled me reply to ask for information on his book (and presumably a link). Also I would see his profile and notice he's also an engineer at a large rails site - which makes me want to hear more from him.

Has anyone else here ever seen this and can show examples of it on HN comments?

Is there another reason some HN posters don't do links in comments, even if they are their own projects but on topic?


For me, the amount of shameless plugging you can get away with is directly related (with weighting) to ones karma score multiplied by thread relevance. I think such a comment in a thread on NSA snooping would have earned many downvotes, here it's just a yes we know :-) at best.

We are all here because we want profit from what we learn here (either improving our minds, or more rarely our bank balances) - and indeed most of us secretly or less secretly want to follow the freelancer / ebook / SaaS product route.

To be reminded of the existence of the route and that it is not an impassable journey is, infrequently, a good thing IMO


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


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!


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.


It was just an example.


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.


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.


You wouldn't even need to have a separate section — you could get the point across well enough with a simple

  render_template('error.html')


it's worse even more then. An example should show a GOOD practice.


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...


And now somebody wants to get that book or die trying https://twitter.com/jeffknupp/status/424925349525200896


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). They are simple but still powerful enough for the majority of use cases.


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.


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!


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?


Or use twisted python and throw simple out the window!

Kidding (sort of), twisted is great :)


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.


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


You can if you use Stripe. Well...you need the CVC number.


You don't actually need the CV code.


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


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


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?


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


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.


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


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?


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.


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.


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


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


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


For someone who created their account 26 days ago, there's probably a lot you don't know... If enough people didn't find it interesting, it wouldn't be here.


That would be saying Justin Bieber songs should be promoted here because there are enough people find him interesting... but we all know what people they are.


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.


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




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: