Posts tagged with API

Sending iOS Push Messages with Urban Airship and BFUrbanAirship

April 25th, 2012

For iFebo I built a server last year to handle sending push messages to Urban Airship, which we use in front of the Apple Push Notification service due to its great scheduling features. To do this I needed a way to interact with the UA API and while it’s got lots of features it’s not terribly… consistent.

So, I wrote my own wrapper library which attempts to give you a more consistent and type-safe way to interact with the API. It’s taken me a long time to make it public and I’ve got some work to do to make it up to date, but I’m glad to give you BFUrbanAirship. You can use it like so:

import com.bubblefoundry.bfurbanairship._
 
val api = new UrbanAirship(app_token, app_secret, app_master_secret, appengine = false)
// schedule a message to be pushed
val message = SimplePushMessage(aps = Some(APS("A push message")))
api.push(message)
 
// get a Stream of all registered devices
api.devices

Tropo is broken

December 10th, 2011

Oh. my. god. What a giant disaster.

I’ve been raving about how awesome Tropo is at handling SMSes to everyone I talk to, but no more. Tropo expects EVERY interaction to be the in the form of a response to a request that they’ve POSTed to your API endpoint.

Everything is part of a session THROUGH THEIR SERVER AND BACK TO YOUR CALLBACK URL. Want to send an SMS? First you need to create a session. Consider the Java library (in Scala):

val result = api.launchSession(token)
if (result.getSuccess) {
  api.message(...)
  ...
}

All valid, type-safe code. launchSession() returns a TropoLaunchResult, which indicates whether the session was successfully created and, if so, its session id.

We can work with this, right? WRONG! As part of the session creation Tropo will throw a session creation object at your callback URL:

00020	0000	11:22:41 PM   	ApplicationInstance[http://miogiro.pr1001.cloudbees.net/tropo.json , sas_2-15-sm1778xfp7g8g0dtropo] starts execution on Thread Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96
00021	0000	11:22:41 PM   	Thread Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96 acquired engine com.tropo.rest.engine.TropoScriptEngine@78ded0e7 of type tropo-web, activeEngines = 4
00022	0000	11:22:41 PM   	Sending TropoML Payload on Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96 [url=http://miogiro.pr1001.cloudbees.net/tropo.json]: {"session":{"id":"3899e36f1ad7650f2848c3ac12332b96","accountId":"notsharing","timestamp":"2011-12-09T23:22:41.612Z","userType":"NONE","initialText":null,"callId":null,"parameters":{"token":"notsharing","action":"create"}}}
00023	0000	11:22:41 PM   	Received non-2XX status code on Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96 [url=http://miogiro.pr1001.cloudbees.net/tropo.json, code=500]
00024	0000	11:22:41 PM   	Thread Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96 returned engine com.tropo.rest.engine.TropoScriptEngine@78ded0e7 of type tropo-web, activeEngines = 3
00025	0000	11:22:41 PM   	ApplicationInstance[http://miogiro.pr1001.cloudbees.net/tropo.json , sas_2-15-sm1778xfp7g8g0dtropo] ends execution on Thread Tropo-Thread-3899e36f1ad7650f2848c3ac12332b96
00026	0000	11:22:41 PM   	sas_2-15-sm1778xfp7g8g0dtropo invalidated
00027	0000	11:22:41 PM   	Instance 3899e36f1ad7650f2848c3ac12332b96 removed

See what’s happening? If you fail to handle it and return a 200 status code (and a Tropo JSON verb?) it kills the session, never mind that I have no need for that. That means it’s literally impossible to send an SMS without a server setup to receive callback messages from Tropo. Oh, and of course you can only specify one URL: you’re supposed to route all the different requests based upon extra parameters you stick in the session creation requests.

It literally boggles the mind how someone could have thought this was a good idea.

Tropo, if you want to fix this and need help send me an email at peter@bubblefoundry.com. Tropo competitors, if you have a sane API to send SMS messages and callbacks to response to received messages (bonus points for a Scala or even Java library) and provide UK numbers that can handle both inbound and outbound SMS, feel free also to email me at peter@bubblefoundry.com.

Mobile APIs Talk Next Wednesday

July 23rd, 2011

Daniel Salber and I will be giving the next Appsterdam Weekly Wednesday Lecture on mobile APIs. The talk is on Wednesday July 27 at 12:30 at Vijzelstraat 20, Amsterdam.

Here is our talk description:

Many mobile apps get data or send data to a server. What are the issues in implementing this kind of apps? In this talk, Peter Robinett and Daniel Salber will share their personal experiences designing and implementing both the server and client (iOS) sides of APIs for mobile apps, taking examples from their apps Smakelijk Amsterdam, Coffeeshoppr, iFebo, and JHM. Starting from questions to ask at the beginning of a project, continuing all the way through to debugging and performance, they will lead you through a broad overview of the implementation of a mobile API in your project.

Working Around Bad Mobile Proxies

May 25th, 2011

For some reason it seems like when you access the internet wirelessly you’re asking for trouble: invariably you’ll run into flakey WiFi connections and weird cellular data problems. There are are two problems that I frequently encounter, WiFi networks hijacking your requests so you log into their site first and cellular data ‘accelerators’ messing up HTTP headers. Both of these are caused by what are essentially proxies breaking normal internet connections and should not be done. However, we live in the real world and so, after first explaining the problem in greater detail, I’ll outline an approach I’ve been mulling over with friends the last few weeks that at least mitigates the poor user experience. Read more »

The Facebook Login Button and logging into your webapp

March 19th, 2011

Recently I’m been working on a single sign-on system (SSO, in the biz) and this week I added support for logging into the SSO via the Facebook Login Button. It’s cool that you can have a user log into you website using Facebook but as it stands there are a few… unfortunate… aspects to the system, all based around one fact: logging into a site using a Facebook account is logging into Facebook.

On first glance that sounds great: if a user isn’t logged into Facebook when they click the Login Button, Facebook asks them to login and then asks the user if they wish to give your app access to their data, the default being the most limited personal data (good). Sounds nice. The Facebook Javascript SDK creates a cookie on your domain with the current user’s Facebook user id and an OAuth access token. You can then make API calls. Great.

However, by using the Login Button you’re tying your app’s basic user state to Facebook. If you’re like me, you want to do some stuff, both client-side and server-side, when a user is logged into your app. Likewise, you want to preform some actions when the user logs out of your app. And we do want to let the user to be able to easily log out: maybe they’re using a computer at an internet cafe, maybe they want to let their sister log into the same site under her own account, maybe they just don’t like leaving themselves logged in. This is where I believe Login with Facebook has some problems.

Let’s assume that we have stand-alone login and logout pages. My experience is with this case, though I believe that my observations stand for other cases. Let’s take the flow in reverse, as I think it will make more sense in the end. The user clicks a link to your logout page. What do you do? Perhaps we’d like to do it all server-side so we can then redirect them to the home page without any prompting. Seems fair enough. So, let’s destroy any sessions (including cookies that sessions might be recreated from) that our website set.

But wait, Facebook created a cookie on our domain with the current user’s access token. Let’s delete that too to make sure our user is logged out of our app via Facebook. But no, that won’t work (more explanation in a bit). I know, you say: let’s use the FB.logout() function. Except that logs you out of Facebook entirely. Remember what I said about the Login Button logging you into Facebook? Well, it logically follows that the logout call logs you out.

Except, why would anyone ever want to do that to their users? It seems like such a bewildering and user-hostile option, to log the user out of Facebook when they think they’re just logging out of your site. And if you make it clear to the user that logging out of your site will also involve logging out of Facebook, who would ever want to do that?

But Facebook is using OAuth 2.0, right? So that means there’s an access token and you can revoke it, right? There definitely is an access token: Facebook nicely gives it to you at the end of a successful login process. However, you can’t programmatically revoke your own access token other than by calling FB.logout(). There’s no function to do that and for the life of me I can’t figure out as a user how to revoke access that I’ve given out. Twitter, in contrast, has a pretty easy to manage Connections page. Update: it’s the Apps You Use page.

All this hints at what I believe to be an underlying assumption: a user will never (should never) want to log out of your site once logged in by Facebook. I think this is fundamentally wrong, but let me skip that issue and explain why being unable to simply logout of the Facebook login of your app (yes, it’s a mouthful) is so annoying.

There are two main ways to detect that a user has logged into Facebook via the Login button. First, you can add an onlogin callback to the button’s XBFML declaration (aside: XFBML is so stupid). Incidentally, this callback is not documented anywhere in Facebook’s documentation. But, it exists and it works. Except, it will never fire if the user is already logged in. The user will be left clicking the Login Button madly and almost definitely not noticing the output in the Javascript error console explaining that the user cannot log in because they are already logged in. No new login, no callback called.

So that doesn’t work, on to the second option. How can we handle the case if the user clicks the login button but they’re already logged in? Luckily you can subscribe to the user.login event, which will eventually fire whenever the user is logged in. That is, it will always eventually fire. This includes when you load your login page with the Login Button: the Facebook API will be initialized, it will detect the user is logged in, and then it will fire the user.login event.

Unfortunately, since this happens all in Javascript and once the DOM has (entirely?) loaded, a user will see page flicker: they will see the login page and then it will disappear after a second if, like in my case, you redirect to another page upon successful login. It is confusing as a user to see the login page, suggesting that I am not logged in, and then to automatically be redirected and the website considers me logged in, all without me ever touching the Login Button.

I think Facebook assumes you’ll avoid this login page flicker by first checking the Facebook cookie left on your domain on the server-side. I am doing that but I run the risk of logging a user into my app based upon expired credentials which are still stored in the cookie, since I refuse to call FB.logout(). So, my website’s logout functionality currently destroys the Facebook cookie but does not call FB.logout(). But, as I mentioned, the user will be logged in automatically via the Javascript API the next time they visit the login page, despite no cookie being present.

In theory we can improve the client-side experience a little bit by first checking if the user is already logged into the website via Facebook. If they are, handle the Facebook login as normal but if, and only if, they are not do we then add the Login Button XFBML to the DOM and parse it. Then, at least, the user is only shown the login button if they need to login, but the fact remains they are still seeing the login page in the first place. In the worse case the page might be mostly empty while all the Javascript is executing to handle an already logged in user.

For this and other reasons we naturally start wondering whether some of this page flicker can be avoided by processing on the server-side. The only solution to potentially using an expired Facebook access token seems to be to use the server-side API’s authentication functionality with the cookie Facebook set, but then you start to fall down the rabbit hole of duplicating all client-side functionality in your server-side code. And if you’re like me, the original reason you chose client-side login and the Login Button was because it was so easy to implement and seemed to have a superior user experience to server-side  authentication (notice Facebook never says ‘login’ when talking about the server-side API).

One thing I’ve noticed is that Facebook’s cookies have quite short life times (like, several hours). I believe that Facebook has made the basic assumption, probably fair in most cases, that a user logging into a website using Facebook generally just wants to use it for a little bit and won’t be too put out if upon returning a day later they have to login again. And so, logout happens essentially magically as access tokens expire.

However, that still seems rather user- and developer-hostile: users will be logged out of the site without knowing it, or the site will keep them logged in based upon a stale access token, or the site must be constantly refreshing its access token behind the scenes. I don’t even know if a site can refresh it behind the scenes and in fact I think it would be somewhat bizarre if they could. The server-side authentication flow I mentioned earlier seems to be the closest thing to this.

To wrap up, I think the basic workings of the Login Button and the ability for a user to login to your website via the Facebook API is a good one, but the system makes some assumptions that are ultimately user-hostile. In some (all?) cases I think Facebook was actually trying to make the user-experience better but their solutions went against established practices for logging users into and out of websites, including facebook.com itself. I don’t have any specific recommendations beyond doing what’s expected and allowing sites to log users out of their sites (expire or destroy the access tokens).

Introduction to the AnyMeta API

September 15th, 2010

Recently I’ve been working on a project for Mediamatic (hopefully more later)  and a key part of my work involves using the REST API for their AnyMeta community management system. Unfortunately there isn’t a lot of documentation out there either for AnyMeta in general or for its API in specific. There’s the user guide if you have an AnyMeta system to manage (but you probably don’t, as Mediamatic normally does that for you) and one document outlining the principles behind the API. The latter has a very important table of API methods that is worth reproducing here:

Method Explanation
anymeta.predicates.get Get a specific data field, for instance the title of a thing.
anymeta.thing.dump Get a full data-dump of a thing, including all references to its images, video files, etc.
anymeta.attachment.create Create an attachment, e.g. upload an image or video into the system
anymeta.edge.add Connect things in the database using edges, semantical links between things.
anymeta.search.live Full-text search on the database
query.execute Advanced, powerful interface for querying the semantic network and the thing database.
identity.search Search the Identity database which stores RFID — ID links
identity.add Add an identity (e.g. an RFID tag) to the system

As you can see from this table, AnyMeta is a system of Things that are linked via Edges. A Thing can have Predicates (essentially, properties or attributes) which give more information about the Thing. The full list of API methods available on your AnyMeta site – and this can change based upon what modules you have installed – can be seen at http://yoursite/services/rest/. For instance, http://www.mediamatic.nl/services/rest/.

Unfortunately the /services/rest documentation is where things get tricky, and so I figured you may need an article with more information. The documentation is automatically generated from the code and unfortunately there doesn’t seem to be enough detail to make everything readily apparent. For example, anymeta.thing.insert takes a data array, but what is that? I’ll try to explain some of these gotchas.

First, we’re going to use the AnyMetaAPI Python library. It’s by Mediamatic, handles all authentication (even OAuth), and even has a CLI. Really, use it. There’s a tutorial on the Mediamatic site but it’s quite brief, so this article will walk you through the whole process from start to finish and give you more tips on how to figure out the API. I’ll be using the AnyMetaAPI library for my examples here and I recommend you also use it for any scripts you write that interface with the API. Of course this then requires that some or all of your application be in Python. Your call.

First, we need to install AnyMetaAPI. Here’s one gotcha: I have been using pip recently to install Python modules and successfully installed AnyMetaAPI using it (pip install AnyMetaAPI from my command line). However, I couldn’t for the life of me figure out why I couldn’t complete an OAuth authentication session. Then I noticed that I had version 1.15 and the latest version is 1.22. Thinking the problem might be fixed by upgrading I tried to do that but, try as I might, I couldn’t get pip to upgrade AnyMetaAPI to the latest version. Carefully reading the pip homepage I discovered the cause: pip cannot install eggs, and the most recent versions of AnyMetaAPI have only been released as eggs. Solution: use easy_install.

Now that you have the AnyMetaAPI module installed you now should have a program called any-registry. It has a few basic options:

$ any-registry
any-registry 1.22
Usage /usr/local/bin/any-registry [opts]  [cmdoptions]

Command is one of:
 list       - List all registry entries
 gui        - Show graphical user interface (linux only)
 add        - Add API endpoint
 del        - Remove API endpoint
 cli        - Commandline interface

The first thing you will need to do is add an API endpoint:

$ any-registry add yourname http://yoursite

yourname should be a short name that you can remember and is easy to type. For example, ‘mediamatic’. The URL is the basic URL of your AnyMeta install, for example ‘http://www.mediamatic.nl/’. The program will ask you to open a special URL on the AnyMeta site where you can authorize the program access to your account there. Once you do that and tell the program (this is where I got stuck, mind my note earlier!), you’re ready to start using the CLI: any-registry cli yourname.

Let’s first get your own information:

>>> api.anymeta.user.info()
{u'auth_info': {u'usa_email': u'peter@bubblefoundry.com', u'usa_modify_date': u'2010-07-21 14:15:44', u'usa_logon_date': u'2010-09-15 08:47:51', u'usa_axo': u'public', u'usa_access_date': u'2010-09-15 08:47:51', u'usa_enabled': u'1', u'usa_axo_section': u'content', u'usa_email_sha1': u'2a27a3eec2c03b53e0861563dcf523dc585282a1', u'usa_confirm_logons': u'0', u'usa_create_date': u'2010-07-21 14:15:44', u'usa_id_ref': u'76', u'usa_confirm': u'', u'usa_prev_logon_date': u'2010-09-15 08:47:51'}, u'preferences': [], u'title': u'Peter Robinett', u'id': u'76', u'axo': [u'content', u'public'], u'aro': [{u'id': u'13', u'name': u'members'}]}

As you can, see an object called api is imported into our CLI’s scope, so we can fire authenticated API calls using its objects and methods. When we call the method correctly a Python dictionary is returned to us and, since we didn’t use the result at all, printed to the console. Of course I can use the result as a normal dictionary:

>>> me = api.anymeta.user.info()
>>> me['id']
u'76'

As the documentation for anymeta.user.info says, you can also pass an id. Let’s try that:

>>> api.anymeta.user.info(76)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: __call__() takes exactly 1 argument (2 given)

Ah ha, another gotcha! I can’t exactly tell you why this is happening (my Python-fu isn’t that great), but I can given you the solution: use keyword arguments. Looking at the API documentation again you’ll notice that the parameter is called person_id, so let’s try that:

>>> api.anymeta.user.info(person_id=76)
{u'auth_info': {u'usa_email': u'peter@bubblefoundry.com', u'usa_modify_date': u'2010-07-21 14:15:44', u'usa_logon_date': u'2010-09-15 08:47:51', u'usa_axo': u'public', u'usa_access_date': u'2010-09-15 08:47:51', u'usa_enabled': u'1', u'usa_axo_section': u'content', u'usa_email_sha1': u'2a27a3eec2c03b53e0861563dcf523dc585282a1', u'usa_confirm_logons': u'0', u'usa_create_date': u'2010-07-21 14:15:44', u'usa_id_ref': u'76', u'usa_confirm': u'', u'usa_prev_logon_date': u'2010-09-15 08:47:51'}, u'preferences': [], u'title': u'Peter Robinett', u'id': 76, u'axo': [u'content', u'public'], u'aro': [{u'id': u'13', u'name': u'members'}]}

Much better! Now let’s create a new Thing and we have the handy method anymeta.thing.insert to do so. But what goes in the data array? A brief discursion1:

The best way to view the potential values of a certain type of Thing is to call anymeta.thing.dump on a similar, existing Thing. Like with the previous method, you need to use a keyword argument. Unfortunately, this time it’s called id. While I can see the attraction of having descriptive id names (person_id when we know the Thing is a Person, id when it’s any Thing), I think this is also an annoying inconsistency and forces developers to constantly check the API documentation to see what the id keywords are called. But this article is about using AnyMeta, not improving it. Instead, let’s consider the output:

>>> api.anymeta.thing.dump(id=199)
{u'create_date': u'2010-09-10 18:20:26', u'axo_section': u'content', u'picture_playable': {u'orientation': u'1', u'height': u'0', u'width': u'0', u'att_id_ref': u'199', u'mime': u'image/jpeg', u'is_picture': u'1'}, u'text': {u'body': u'', u'redirect_uri': u'', u'title_short': u'', u'subtitle': u'', u'language': u'en', u'title': u'An Image', u'chapeau': u'', u'label': [], u'intro': u''}, u'share': u'1', u'is_thing': 1, u'file': {u'orientation': u'1', u'is_media': u'1', u'height': u'0', u'width': u'0', u'file_blob': {u'encode': u'base64', u'data':
....

anymeta.thing.dump really does give you everything, so watch out calling it on image Things like I’ve done: you get the entire image data in base64 encoding dumped to your screen!

Note, you may have all your dump commands fail like so:

>>> api.anymeta.thing.dump()
Traceback (most recent call last):
  File "", line 1, in 
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 21, in __call__
    return self.api.doMethod(self.part, kwargs)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 161, in doMethod
    return self._getPage(http_method, str(url), postdata, headers, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 226, in _getPage_httplib
    return self._processPage(page, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 210, in _processPage
    raise AnyMetaException(err['code'], err['msg'])
anymeta.api.base.AnyMetaException: method-not-available: The method anymeta.thing.dump is not available

If so this means that your AnyMeta site does not have the ThingDump module installed. To enable it your site administrator will need to go to http://yoursite/admintools/index/ModuleLoader and install the module. Then you should see your commands work and the anymeta.thing.dump method show up in the /services/rest documentation.

Perhaps more useful than the image I dumped is to dump my information:

>>> api.anymeta.thing.dump(id=76)
{u'create_date': u'2010-07-21 14:15:43', u'axo_section': u'content', u'text': {u'body': u'', u'redirect_uri': u'', u'title_short': u'', u'subtitle': u'', u'language': u'en', u'title': u'Peter Robinett', u'chapeau': u'', u'label': [], u'intro': u''}, u'share': u'1', u'is_thing': 1, u'trust': {u'edit': {u'predicate': None, u'level': u'ME'}, u'react': {u'predicate': None, u'level': u'MEMBERS'}, u'link': {u'predicate': u'KNOWS_OF', u'level': u'PREDICATE'}, u'view': {u'predicate': u'false', u'level': u'EVERYONE'}}, u'modifier_id': u'http://173.45.230.55/id/76', u'category': [], u'authoritative': u'1', u'symbolic_name': u'', u'pubstate': u'1', u'masked': u'0', u'props': [], u'type': {u'symbolic': {u'1': u'NOMAIL'}}, u'owner_id': u'http://173.45.230.55/id/76', u'pub_date_end': u'9999-01-01 00:00:00', u'views': u'0', u'retirement_date': u'9999-01-01 00:00:00', u'auth': {u'confirm_logons': u'0', u'enabled': u'1', u'email': u'peter@bubblefoundry.com', u'confirm': u''}, u'uri_abs': u'http://173.45.230.55/person/76/en', u'lang': {u'en': {u'body': u'', u'redirect_uri': u'', u'title_short': u'', u'subtitle': u'', u'language': u'en', u'title': u'Peter Robinett', u'chapeau': u'', u'label': [], u'intro': u''}}, u'kind': u'PERSON', u'name': {u'full': u'Peter Robinett'}, u'pub_date_start': u'1970-01-01 00:00:00', u'uri': u'/person/76/en', u'edge': [{u'kind': u'TYPE', u'symbolic_name': u'NOMAIL', u'edge': {u'predicate': u'HAS_TYPE', u'create_date': u'2010-07-21 14:15:46', u'uuid': u'a35c66cd-e622-102d-a1f5-4040ad2de637', u'authoritative': u'1', u'object_id': u'http://mediamatic.nl/ns/anymeta/2008/type/nomail', u'predicate_id': u'http://www.w3.org/2000/01/rdf-schema#Class', u'creator_id': u'http://173.45.230.55/id/76', u'object_symbolic_name': u'NOMAIL', u'pending_import': u'0', u'modify_date': u'2010-07-21 14:15:46', u'object_kind': u'TYPE', u'data': None, u'order': u'9999', u'modifier_id': u'http://173.45.230.55/id/76', u'owner_id': u'http://173.45.230.55/id/76'}}], u'modify_date': u'2010-07-21 14:15:46', u'activity': u'0', u'axo': u'persons', u'resource_uri': u'http://173.45.230.55/id/76'}
>>> api.anymeta.user.info()
{u'auth_info': {u'usa_email': u'peter@bubblefoundry.com', u'usa_modify_date': u'2010-07-21 14:15:44', u'usa_logon_date': u'2010-09-15 08:47:51', u'usa_axo': u'public', u'usa_access_date': u'2010-09-15 08:47:51', u'usa_enabled': u'1', u'usa_axo_section': u'content', u'usa_email_sha1': u'2a27a3eec2c03b53e0861563dcf523dc585282a1', u'usa_confirm_logons': u'0', u'usa_create_date': u'2010-07-21 14:15:44', u'usa_id_ref': u'76', u'usa_confirm': u'', u'usa_prev_logon_date': u'2010-09-15 08:47:51'}, u'preferences': [], u'title': u'Peter Robinett', u'id': u'76', u'axo': [u'content', u'public'], u'aro': [{u'id': u'13', u'name': u'members'}]}

I’ve also included the anymeta.user.info output so that you can see how much more information anymeta.thing.dump returns. Particularly interesting is how dictionaries are nested:

u'text': {u'body': u'', u'redirect_uri': u'', u'title_short': u'', u'subtitle': u'', u'language': u'en', u'title': u'Peter Robinett', u'chapeau': u'', u'label': [], u'intro': u''}

This nesting needs to be preserved when creating a new Thing, while the same names must be used. So here’s how we can create a new, generic Thing:

>>> api.anymeta.thing.insert(data={'text': {'title': 'Test Thing'}})
Traceback (most recent call last):
  File "", line 1, in 
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 21, in __call__
    return self.api.doMethod(self.part, kwargs)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 161, in doMethod
    return self._getPage(http_method, str(url), postdata, headers, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 226, in _getPage_httplib
    return self._processPage(page, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 210, in _processPage
    raise AnyMetaException(err['code'], err['msg'])
anymeta.api.base.AnyMetaException: 0: Not allowed to insert a new UNKNOWN into axo public

Oops, it didn’t like that! The problem is that I didn’t specify the Kind of my Thing. What what Kinds are there? This, I’m afraid is still somewhat of a mystery to me. Spelunking through the AnyMeta source code (something probably not possible for you), I have found the following Kinds: ARTICLE, ATTACHMENT2, ARTEFACT, NOTE, SET, PERSON3, LOCATION, INSTITUTION, LISTPUBLISH, LISTEDIT, LANGUAGE, TAG, KEYWORD, THEME, TYPE, ROLE, TPLSCOMP, TPLPAGE, TOPIC, and of course UNKNOWN. Let’s just make our Thing an ARTICLE for now:

>>> api.anymeta.thing.insert(data={'text': {'title': 'Test Thing'}, 'kind': 'ARTICLE'})
{u'rsc_id': theID, u'edit_uri': u'http://yoursite/edit/theID', u'rsc_uri': u'http://yoursite/id/theID'}

If you visit the URL corresponding to the rsc_uri key you should see a blank AnyMeta page (plus the various navigation elements and such) with ‘Test Thing’ in large type at the top. Cool, we created a Thing using the API! Now let’s update our thing, using the id in the rsc_id value (again with the inconsistent names of id values!), to add a body (i.e. article text):

>>> api.anymeta.thing.update(id=202, data={'text': {'body': 'This is some body text.'}})
Traceback (most recent call last):
  File "", line 1, in 
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 21, in __call__
    return self.api.doMethod(self.part, kwargs)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 161, in doMethod
    return self._getPage(http_method, str(url), postdata, headers, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 226, in _getPage_httplib
    return self._processPage(page, format)
  File "/Library/Python/2.6/site-packages/AnyMetaAPI-1.22-py2.6.egg/anymeta/api/base.py", line 210, in _processPage
    raise AnyMetaException(err['code'], err['msg'])
anymeta.api.base.AnyMetaException: 1: Please supply a thing_id
>>> api.anymeta.thing.update(thing_id=202, data={'text': {'body': 'This is some body text.'}})
[]

See my mistake and why inconsistent id names annoy the heck out me? Anyway, if you refresh the url you just visited you should now see our new text underneath the title. That empty list ([]) returned by anymeta.thing.update unfortunately indicates that our request succeeded. If I had my way you’d get a nice dictionary with fields from the Thing relevant to its Kind, at least including the fields we updated, to indicate success and to make subsequent queries easier. Oh well.

Finally, let’s try working with Edges. You may have noticed on our Thing page that it says that it was created by whatever authenticated account you’re using with any-registry. That’s great, but let’s add an actual authorial relationship between me and this thing. This relationship will be reflected many pages throughout the page and site, even to the point of AnyMeta putting meta tags in the HTML indicating that I’m the author. So, we can mark me as the AUTHOR of the Thing like so:

>>> api.anymeta.edge.add(id=202, object=76, predicate='AUTHOR')
{u'predicate': u'AUTHOR', u'date_start': None, u'title': u'Peter Robinett', u'edg_id': u'16', u'date_end': None, u'object': u'76', u'title_short': u'', u'value': None, u'predicate_id': u'7', u'kind': u'PERSON', u'order': u'9999', u'subject': u'202'}

Notice that we’re marking the authorship from the ARTICLE, with Thing id 202, to the PERSON Thing with id 76 using the AUTHOR predicate. If you reverse the order of the ids so that id=76 and object=202 then the article would be the author of me and we wouldn’t want that! anymeta.edge.add gives us some reasonable return values and we can use the new edg_id (I didn’t say anything, honest! =) to update or remove the edge in the future. You should now

Like with Kinds there’s no readily available list of Predicates. Here are the ones I’ve dug up: ABOUT, ACTOR, AUTHOR, BLOCKS, CHILD, DOCUMENT, EXTRACONTEXT, FIGURE, FRIEND, HAS_TYPE, ICON, INTEREST, KIN, KNOWS, KNOWS_OF, LIVED, LOCATED_IN, ORGANISED_BY, PRESENTED_AT, PUBLISHED_BY, RELATED, RESOURCE, SETMEMBER, SETMOTHER, SIBLING, SPOUSE, SUBPROPERTY_OF, TAKEN_AT, VISITOR, WORKS_FOR. It is my understanding the Predicates (and Kinds) can vary from AnyMeta install to install depending on the domain model used, so you may not have the exact same Predicates as the ones I just listed, though I would imagine that you can rely on having Predicates like AUTHOR, LOCATED_IN, and TAKEN_AT.

In this article I’ve walked you through setting up AnyMetaAPI, connecting to an AnyMeta site, and doing some basic manipulations with the API. I’m still learning the ins and outs of the API, so there may be mistakes here or better ways to do things. However, I hope this has been useful and if you have any questions after reading this please ask them in the comments section.

  1. And yes, that is a word, despite what my spellchecker may say. []
  2. What my image Thing was, you may have noticed. []
  3. In the middle of the dump of my Thing is the declaration that the Thing is a PERSON. []

TLDapi

December 6th, 2009

I whipped TLDapi up in the last few days. If you’re a programmer and you want a way to check if an exotic top level domain a user sent you is real, use the API to check.

oAuth on App Engine, Part 2

May 24th, 2009

In my previous post I described how to use Google’s federated login to get an oAuth access token. Now that we’ve stored our access token, we’re going to want to use it in future requests to access the user’s data. Here’s how:

# set up service
gdata_service = gdata.service.GDataService()
gdata.alt.appengine.run_on_appengine(gdata_service)
gdata_service.SetOAuthInputParameters(gdata.auth.OAuthSignatureMethod.HMAC_SHA1, settings.GOOGLE_CONSUMER_KEY, settings.GOOGLE_CONSUMER_SECRET)

# build access_token object and signed request
scopes = [settings.GOOGLE_ANALYTICS_ACCOUNT_URI, settings.GOOGLE_ANALYTICS_DATA_URI]
oauth_input_params = gdata.auth.OAuthInputParams(gdata.auth.OAuthSignatureMethod.HMAC_SHA1, settings.GOOGLE_CONSUMER_KEY, settings.GOOGLE_CONSUMER_SECRET)
access_token = gdata.auth.OAuthToken(scopes=scopes, oauth_input_params=oauth_input_params)
access_token.set_token_string(person.accessToken)
gdata_service.current_token = access_token

# request feed
feed = data_service.GetFeed(settings.GOOGLE_ANALYTICS_ACCOUNT_URI)

feed is a GDataFeed object which can easily be iterated over, like so:

for entry in feed.entry:
    print entry.title.text

To be honest, I’m not sure if the scopes are necessary but I included them for the sake of completeness. Setting oauth_input_params for the oAuth token is essential and something that took me a while to figure out – I had assumed that setting the parameters for the Gdata service would be enough.

I am directly assigning the access token to the Gdata service. I had originally tried to use the setter – gdata_service.SetOAuthToken(access_token) – but had problems and switched to direct assignment. However, my problem could very well have been somewhere else, so feel free to try using the method.

Finally, you may have noticed that I’m querying the Analytics Data API. I hope to have more to announce about that soon. In the meantime, I’ll note that gdata-python-client doesn’t have specific Analytics support, which is why I’m using the generic Gdata service.