Skip to content

Instantly share code, notes, and snippets.

@dorkitude
Created November 27, 2011 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dorkitude/1398138 to your computer and use it in GitHub Desktop.
Save dorkitude/1398138 to your computer and use it in GitHub Desktop.
A sample use case for DSTruct: Remote Resource
"""
A sample use case for DSTruct ( https://github.com/dorkitude/dstruct )
Let's pretend we're implementing the in-app purchase feature of a mobile game,
and there's a remote payments API over which we have no control. To make
matters worse, their API doesn't support versioning and their client library
isn't properly encapsulated, so when they make an update to the API spec, we
have to handle it in our code. As a result, our spec demands type checking not
only to preempty failures when we post resources, but to vet the response.
In this code snippet, we will create an Account using the payments API, then
retrieve it using the same.
"""
# Before DStruct
# ==============
# Creating an Account
# -------------------
# 1. Make an account dictionary:
account = {
"id": account_id,
"email": email_address,
"credit_card_mask": credit_card_mark,
"creation_timestamp": utils.timestamp(),
}
# 2. Validate the account dictionary:
validator.validate_account(account) # validates the dict against a schema
# 3. Hit the API to generate the account in the remote server:
api_client.post("Account", account_dict) # returns a dict
# Retrieving an Account
# ---------------------
# 1. Populate a local dictionary with remote values:
account = api_client.get("Account", account_id) # returns a dict
# 2. Validate the response's schema:
validator.validate_account(account) # validates the dict against a schema
# With DStruct
# ============
# Creating an Account
# -------------------
# 1. Make an account instance:
account = Account({
"id": account_id,
"email": email_address,
"credit_card_mask": credit_card_mark,
"creation_timestamp": utils.timestamp(),
})
# 2. Validate the account instance:
pass # Thanks to DStruct, we don't have to do this explicitly
# 3. Hit the API to generate the account in the remote server:
api_client.post("Account", account_dict) # returns a dict
# Retrieving an Account
# ---------------------
# 1. Populate a local object with remote values:
account = Account(api_client.get("Account", account_id)) # returns an Account instance
# 2. Validate the account instance:
pass # Thanks to DStruct, we don't have to do this explicitly
# A sample definition of the Account
class Account(DStruct):
"""
Wraps the payments API's "Account" resource.
"""
# Schema:
id = DStruct.RequiredAttribute()
creation_timestamp = DStruct.RequiredAttribute()
credit_card_mask = DStruct.RequiredAttribute(int)
email = DStruct.RequiredAttribute(EmailAddress)
# Boy, sure am glad I'm using a class here, since I can add methods!
@property
def creation_date(self):
"""
Returns a ISO 8601-style creation date, on which our UI is dependent.
This property is used for backwards compatibility, because the payments
API was modified to return a POSIX integer called creation_timestamp.
:returns: String.
`self.creation_date` used to be
"""
return date_utils.iso_date_from_(self.creation_timestamp)
"""
In the "Before DStruct" case, schema validation is handled in a validation
system, which must be explicitly called each time we play with a dictionary
account (not to mention it must be programmed). This case therefore violates
DRY, because the piece of system knowledge "we must always validate the
dictionaries used in API calls" is not stored in one place, but in many (i.e.
every time we use `api_client.get` or `api_client.post` anywhere in our
codebase). Moreover, since we're using an instance of a class, we can now add
arbitrary methods, such as the handy `self.creation_date` method, to the class,
and the rest of our codebase, which may be reliant on those fields, need not be
changed if the API changes.
Notes:
* Realistically, Account would probably not directly extend DStruct, but would
rather inherit from something called BaseRemoteResource, which would itself
extend DStruct and handle the API's irresponsible, versionless
respecification of the creation date field, on a system-wide basis -- if
I were to write a system like this, it would probably contain refresh() and
save() methods as well. BaseRemoteResource would also probably demand `id`
and `creation_timestamp`, so individual resource classes (such as Account)
need not violate DRY for schema rules.
* We would probably also not call `api_client.get("Account", account_id)`
directly in all of our implementation code, but BaseRemoteResource would
rather provide a classmethod that can be called like this:
`Account.get_by_id(account_id)`. Because who knows? Our game might
get so large that we want to handle all of our own payments. Encapsulate,
encapsulate, encapsulate.
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment