7 ms·
Python and Flask Are Powerful
- zrail 13y agoThis 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>
- git-pull 13y agoFirst, 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?
- lifeisstillgood 13y agoFor 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
- hcarvalhoalves 13y agoCelery 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
- zrail 13y agoAh, didn't know about RQ. Thanks!
- shavenwarthog2 13y agoAgreed. I got my first job running correctly with Rq in fifteen minutes, and was able to go on to other things. Recommended!
- deleted 13y ago[deleted]
- jknupp 13y agoI 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.
- zrail 13y agoSure. 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.
- abritishguy 13y agoI 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.
- elithrar 13y ago> 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).
- abritishguy 13y agoI 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 13y agoThat'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!
- smoyer 13y agoUsing 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 13y agoI'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.
- orf 13y agoyes, it should
- smoyer 13y agoORMs 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.
- zzzeek 13y agonot 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.
- stefantalpalaru 13y agoApparently not powerful enough to convince the programmer that mixing Python and HTML is a bad idea.
- coldcode 13y agoGood 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.
- hackerboos 13y agoYou can if you use Stripe. Well...you need the CVC number.
- abritishguy 13y agoYou don't actually need the CV code.
- jknupp 13y agoI never actually store (or see) any CC information. All of that is handled on Stripe's end.
- deleted 13y ago[deleted]
- freyr 13y agoPython 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 13y agoAgreed. 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 13y agoI 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 13y agoThere are more people who don't know how to code than do.
- hownottowrite 13y agoGreat article. Last summer I put together a concept app for a micro-coding marketplace using Python and Flask. Stripe made taking payments insanely simple.
- semerda 13y agoDitto. Stripe rocks! Simple to integrate and get it up and running in few mins.
- vsl 13y agoGlobal 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.
- ossdev1 13y agoI don't know is this a forum for Python promotion?
- rmrfrmrf 13y agoI'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 13y agoExactly. Besides they are downvoting all critical comments, so nobody can disagree with mainstream's publicity.
- deleted 13y ago[deleted]
- jknupp 13y agoFor 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.
- ossdev1 13y agoThat 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.
- f4stjack 13y agoJust 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.
- sputnikus 13y agoAnd now somebody wants to get that book or die trying https://twitter.com/jeffknupp/status/424925349525200896 https://twitter.com/jeffknupp/status/424925349525200896
- gingerlime 13y agoWhat about paypal as a payment option however? Isn't this still a pretty major PITA to support?
- abritishguy 13y agoNot if the target market is devs, we hate paypal.
- adnam 13y agoAlso, 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)
- ansgri 13y agoDoes really work, 20 s or less. The download seems to be slow though.
- deleted 13y ago[deleted]
- workhere-io 13y agoConsidering 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 13y agoIt was just an example.
- workhere-io 13y agoAn 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.
- straws 13y agoYou wouldn't even need to have a separate section — you could get the point across well enough with a simple render_template('error.html')
- emmelaich 13y agoAn 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.
- Walkman 13y agoit's worse even more then. An example should show a GOOD practice.
- rtpg 13y agoslightly 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_ 13y agoI 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.
- notastartup 13y agois 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
- sighol 13y agoThat 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?
- steffan 13y agoI'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.
- mox1 13y agoI 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.
- iurisilvio 13y agoPoint 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.
- jwmerrill 13y agoThe 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!
- dhon_ 13y agoOr use twisted python and throw simple out the window! Kidding (sort of), twisted is great :)
- workhere-io 13y agoThe 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?
- windexh8er 13y agoI'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...
- squigs25 13y agoCool - pretty elegant solution to a common problem. Interestingly, the solution was "enabled" by recently available technology packages, like stripe (and flask).