3 ms·
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 li
by smoyer 13y ago
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 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.
- jknupp 13y agoBoth of these have been fixed. Thanks for pointing out the off by one error and redundant session.add()
- j-kidd 13y agoI 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
- icebraining 13y agoEven 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.html http://www.postgresql.org/docs/current/static/mvcc-intro.htm...