To Do

Things that still need to be done, or at least might be useful if done.

Performance

I instrumented the code to determine how much time is taken by all the dynamic construction for the APIInfo, _getAPI, _makeAPIContext, etc stuff. On my mac that contributes about 189 microseconds (i.e., less than 1 millisecond) of overhead. It's just not a factor versus the round-trip times to the NumerousApp server.

If you time the end-to-end calls you will be up in the 280 millisecond range with the bulk of this being actual on-the-wire time (i.e., beyond our control). The requests library conveniently puts the on-the-wire elapsed time into the response object which the Numerous class makes available via the statistics (see below). Thus the maximum theoretical API rate is about 3 to 4 requests per second back and forth to the Numerous server from a single thread. You can definitely get more with multiple threads/processes. Your mileage may vary of course depending on network conditions.

By default the round-trip time (in floating point seconds) of the most recent API request is available in nr.statistics['serverResponseTimes'].

If you force that element to be an array then the code will keep the most recent N round-trip times (as determined by the size of the array). So, for example:


    nr.statistics['serverResponseTimes'] = [0]*10


will set things up so that the last 10 response times at the requests.request() call level are always available in 'serverResponseTimes'. The most recent time will be the zero'th element in the array.

If you hand instrument the code and perform more experiments you will find there is 4-6 milliseconds of extra overhead buried somewhere in the requests module itself. I haven't been able to figure out what that is; I'm guessing it has to do with sockets being closed when objects go out of scope.

None of this is remotely significant in the face of 280msec on-the-wire time.

Exception Wrap Subclass

By design the Numerous and NumerousMetric classes bubble up most errors as Exceptions and do not hide low-level errors some of which are a little silly.

For example, deleting a metric photo when the metric has no photo causes a NumerousError exception, code 404. Arguably that error should be ignored. Along the same lines, deleting an interaction that doesn't exist causes a NumerousError exception with code 200 (OK). This is because the server returns code 204 (No Response) when an interaction is successfully deleted (so this is used as the "expected good response" code) and returns code 200 ("OK") when the specified interaction doesn't exist. This is understandable from a semantic invariant point of view - the interaction doesn't exist in either case after the call, so "OK" is a perfectly reasonable response.

However it's worth noting that the server is quite inconsistent on this (e.g., 404 for deleting a metric photo that doesn't exist vs 200 for deleting an event that doesn't exist).

It seemed best to allow all these exceptions and raw idiosyncrasies to bubble up in case you needed to act on them for some reason. However, in the cases where you don't really want to know all this detail it can be a pain. And arguably some of it might be server-implementation-specific and hiding some of those details might be a Good Thing.

So I think there should be two subclasses, NumerousWrap and NumerousMetricWrap, that would do things like this:

class NumerousWrap(Numerous):
    def ping(self):
        try:
            Numerous.ping(self)
        except NumerousAuthError:
            return False
        return True

and so forth, overriding (wrapping) just those methods that raise exceptions and handling the "we don't really care" cases accordingly.