def fetch(self, service_id, service_author_id, service_event_id, callback):

    asm = self.get_author_service_map(service_author_id)

    args = {'oauth_token': asm.access_token,
            'v': 20120130}

    url = '%s%s%s?%s' % (self.oauth_config['endpoint'],
                         CHECKIN_RESOURCE,
                         service_event_id,
                         urllib.urlencode(args))

    event_json = json_serializer.load(urllib2.urlopen(url))

    # check for error
    if event_json['meta']['code'] != 200:
      raise Exception('Foursquare error response: %s' % event_json['meta']['code'])

    '''
      TODO: there should be a generalized mechanism for pruning unwanted properties from
            the json

      With Foursquare we're going to eliminate the user property (we know all about the user) and
      it doesn't appear in the checkin definition returned by "users/self/checkins" apparently
      by design as foursquare designates this event as optional and the user context is clearly
      defined by the call

      The following two properties don't appear in the "users/self/checkins" resource so each new
      foursquare event will immediately update.  If the user executes another checkin within
      60 minutes its possible the collector will get the event again because of the MOST_RECENT_OVERLAP
      window causing this event to "flap".  It's minor but noteworthy.

      del checkin_obj['score']
      del checkin_obj['venue']['specials']
    '''

    checkin_obj = event_json['response']['checkin']

    prune_dictionary(checkin_obj, self.PRUNE_ITEMS)

    interpreter = FoursquareEventInterpreter(checkin_obj, asm, self.oauth_config)

    callback(create_foursquare_event(asm.author_id, CURRENT_STATE, service_author_id, interpreter.get_id(), checkin_obj))
  def fetch(self, service_author_id, callback):

    super(FoursquareEventCollector, self).fetch(service_author_id, callback)

    state = self.fetch_begin(service_author_id)

    self.fetch_log_info(state)

    asm = state['asm']

    args = {'oauth_token': asm.access_token,
            'v': 20120130}

    args['limit'] = LIMIT
    args['offset'] = 0

    # get only events since last update or past year depending on if this
    # is the first collection of not
    if asm.most_recent_event_timestamp:
      after_time = calendar.timegm((asm.most_recent_event_timestamp -
                                    self.MOST_RECENT_OVERLAP).utctimetuple())
    else:
      after_time = calendar.timegm((datetime.utcnow() -
                                    self.NEW_LOOKBACK_WINDOW).utctimetuple())
    args['afterTimestamp'] = after_time

    url = '%s%s?%s' % (self.oauth_config['endpoint'], USER_CHECKINS, urllib.urlencode(args))

    total_accepted = 0
    while url and total_accepted < self.MAX_EVENTS:

      raw_json = json_serializer.load(urllib2.urlopen(url))

      # check for error
      if raw_json['meta']['code'] != 200:
        raise Exception('Foursquare error response: %s' % raw_json['meta']['code'])

      # terminate if the response has no more events/checkins
      if len(raw_json['response']['checkins']['items']) == 0:
        break

      # for each element in the feed
      for post in raw_json['response']['checkins']['items']:

        prune_dictionary(post, self.PRUNE_ITEMS)

        interpreter = FoursquareEventInterpreter(post, asm, self.oauth_config)

        if self.screen_event(interpreter, state):
          total_accepted = total_accepted + 1
          callback(create_foursquare_event(asm.author_id, CURRENT_STATE, service_author_id, interpreter.get_id(), post))

      # for

      if not url:
        break

      # setup next request
      args['offset'] = args['offset'] + LIMIT
      url = '%s%s?%s' % (self.oauth_config['endpoint'], USER_CHECKINS, urllib.urlencode(args))

    # terminate the fetch
    self.fetch_end(state)