My Journey with Python

I’ve been working at my current job for over 10 years now (I started in May of 2014, I can barely believe it’s been this long). In that time, I’ve gone from knowing the very basics about how websites work and how the Internet itself functions on a small scale to building out solutions for customers that deal with request rates in the hundreds or thousands of requests per second. Through that time, I’ve had to learn how to quickly adapt and learn new ways of working. One of the biggest things that I’ve done to improve that is learned how to effectively write Python scripts.

Laptop on a desk with code editor on the screen Photo Christopher Gower on Unsplash

Most of this started out with utilities that make my life easier. Looking up account IDs for on-the-fly utilities. Quickly checking if a property contains a certain rule. Getting a birds eye view of a customer account for strategic resources. Even making bulk configuration changes across the entire account in a matter of minutes rather than hours. I’ve since expanded this into more utilities that can help my coworkers out with their own use cases. A major item that I’m working on right now is a Python wrapper for our APIs that helps to eliminate the need to build out any sort of “plumbing code” from scratch every time. Instead of going like this:

  • Have an idea for a project
  • Figure out how to interface with the APIs
  • Realize you don’t have the right dependencies
  • Read through the API documentation again because it’s been over a year since you last used it
  • Give up entirely

It’s now more something like this:

  • Have an idea for a project
  • Include a simple Class file in the project
  • Use well-documented object references to generate API calls and focus on your own logic

The work I’m doing

I’ve been working on building out templates and examples, as well as fleshing out the documentation to make this as easy as possible to utilize. I’m also working on learning the best way to implement rate limiting within the Class file itself to help prevent runaway use of the APIs - there have been a few instances where someone let a script run unchecked and it resulted in temporary outages or errors that impacted other users. I’d much rather not be on the responsible end of this. As it stands right now, the current mechanism I’m using is a counter system within the Class parameters itself, which can refresh once a minute. The time of the current call is also being stored as a separate parameter, which is renewed every time a call is made and compared with the previous timestamp. This comparison is used to control when we should refresh the total number of allowed requests within the same minute.

Within a given minute, the code currently only allows 50 requests to go through, at which point it will sleep the script for 60 seconds to reduce pressure off of the API endpoint. This is more of a safety mechanism with some relatively sane default values to allow for multiple users to run these at the same time without causing much harm to the systems. For endpoints that return their own rate limiting information, it hooks into the responses there to determine the correct values to use (some of which are far higher than what my script will allow).

Sample Code

Here’s sample code that shows how I’m handling this. I do not claim that this is either an efficient or preferred solution. This was more something that I came up with on my own to help provide my coworkers with the ability to make calls to our APIs without also overrunning the endpoints with a flurry of requests due to misconfiguration.


current_epoch_time = time.time()

# Compare the current epoch time with the stored time.
# If it's been more than one minute, reset the rateLimitRemaining
# variable
if current_epoch_time - self.current_epoch_time > 60:
    self.current_epoch_time = current_epoch_time
    self.rate_limit_remaining = self.rate_limit
    self.logger.debug(
        "%s - Resetting Rate Limit Remaining to %s",
        current_time,
        self.rate_limit,
    )

# Don't allow the Rate Limit Remaining to use
# more than 25% of total rate limit.
if self.rate_limit_remaining / self.rate_limit < 0.25:
    print(
        "%s - Waiting 60 seconds before processing to remain under"
        "rate limit.",
        current_time,
    )
    self.logger.info(
        "%s - Waiting 60 seconds before processing to remain under"
        "rate limit.",
        current_time,
    )
    time.sleep(60)

# Decrement the rateLimitRemaining variable for apps that don't return
# rate information
if use_custom_rate_limits is False:
    self.rate_limit_remaining -= 1
self.logger.debug(
    "%s - Rate Limit Remaining: %s /"
    "Rate Limit: %s /"
    "Percent of Rate Limit Remaining: %s",
    current_time,
    self.rate_limit_remaining,
    self.rate_limit,
    self.rate_limit_remaining / self.rate_limit,
)

Disclaimers

Again, I don’t claim to be a great developer. I mostly am self-taught (although I did have a few programming courses while at college, most of which helped give me a better understanding of the way to think through problems rather than direct development experience). I also take no responsibility of this code doesn’t work for your specific use case, nor will any calls for assistance really be heeded. I do this mostly on my own time or as part of work and don’t intend to release this to the public (yet). If I end up with a solution that I feel like could be really beneficial, then I’d consider releasing it to the public. In its current form, there’s not much benefit here.