A library for interacting with JPL Horizons I'm writing a tiny Python library that fetches data from Horizons, JPL's solar system data and ephemeris computation service. An overview of their batch CGI is here. While the script is simple, I would like to make it as clear and standardized as possible.
The idea is to initialize the HorizonsRequest class with a location for the observer (center), target (COMMAND), datetime, and the the data you want (quantities). The response can be retrieved as either a complete list of the lines sent back or as a dictionary of the requested values.
Example request
A request for the angle of Jupiter (599), its distance, and light travel time in relation to Earth (399) at the current time. Horizons users can find these numeric ids using the web-interface.
request = horizons.HorizonsRequest(&quot;399&quot;, &quot;599&quot;, datetime.utcnow(), &quot;2,20,21&quot;)
request.send()

dictionary = request.get_dictionary()
for key in dictionary:
    print(key, &quot;:&quot;, dictionary[key])

Library
import sys
import urllib.request
import math
from datetime import datetime, timedelta

def get_julian_datetime(date):
    # Ensure correct format
    if not isinstance(date, datetime):
        raise TypeError('Invalid type for parameter &quot;date&quot; - expecting datetime')
    elif date.year &lt; 1801 or date.year &gt; 2099:
        raise ValueError('Datetime must be between year 1801 and 2099')

    # Perform the calculation
    julian_datetime = 367 * date.year - int((7 * (date.year + int((date.month + 9) 
    / 12.0))) / 4.0) + int((275 * date.month) / 9.0) + date.day + 1721013.5 
    + (date.hour + date.minute / 60.0 + date.second / math.pow(60,2)) / 24.0 
    - 0.5 * math.copysign(1, 100 * date.year + date.month - 190002.5) + 0.5

    return julian_datetime

class HorizonsRequest:
    def __init__(self, center, target, datetime, quantities):
        self.keys = {
            &quot;CENTER&quot;: center,
            &quot;COMMAND&quot;: target,
            &quot;QUANTITIES&quot;: quantities,
            &quot;TLIST&quot;: str(get_julian_datetime(datetime)),
            &quot;MAKE_EPHEM&quot;: &quot;YES&quot;,
            &quot;OBJ_DATA&quot;: &quot;NO&quot;,
            &quot;TABLE_TYPE&quot;: &quot;OBSERVER&quot;,
            &quot;CAL_FORMAT&quot;: &quot;BOTH&quot;,
            &quot;ANG_FORMAT&quot;: &quot;DEG&quot;,
            &quot;CSV_FORMAT&quot;: &quot;YES&quot;
        }
        self.response = []
        self.dictionary = {}
    
    def set_key(self, key, value):
        self.keys[key] = str(value)

    def delete_key(self, key):
        self.keys.pop(key, None)    

    def send(self):
        # create request
        request = &quot;https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&quot;
        for key in self.keys:
            request += &quot;&amp;&quot; + key + &quot;='&quot; + self.keys[key] + &quot;'&quot;

        # send request
        file = urllib.request.urlopen(request)
        for line in file:
            self.response.append(line.decode('utf-8').replace('\n', ''))

    def get_dictionary(self):
        keys = []
        values = [] 

        # locate data position in response
        i = 0
        while i &lt; len(self.response):
            if &quot;$$SOE&quot; in self.response[i]:
                keys = self.response[i - 2].split(&quot;,&quot;)
                values = self.response[i + 1].split(&quot;,&quot;)
            i += 1

        # create dictionary
        i = 0
        while i &lt; len(keys):
            self.dictionary[keys[i].strip()] = values[i].strip()
            i += 1

        return self.dictionary

    def get_response(self):
        return self.response

Any feedback would be greatly appreciated.
Use Requests
urllib is kind of a pain to use. Use Requests instead. Among other things, all of this code:
    for key in self.keys:
        request += &quot;&amp;&quot; + key + &quot;='&quot; + self.keys[key] + &quot;'&quot;

will go away. Requests has query string formatting from a dictionary.
Julian conversion
It's doubtful that you need to be doing this yourself. Find a library such as https://pypi.org/project/julian/ .
Combined-inequality syntax
date.year &lt; 1801 or date.year &gt; 2099

should be
not (1800 &lt; date.year &lt; 2100)

Squaring
math.pow(60,2)

should just be
60**2

Late serialization
I would recommend that HorizonsRequest.keys (which, by the way, is not only a collection of keys - it's a dictionary, so it's misnamed) not be constructed until it's needed in send. Nor should you expose arbitrary set_key / delete_key. In theory you should understand enough about the request format that information to create it is stored in more strongly-typed variables that can receive better validation. For instance, do not store
&quot;OBJ_DATA&quot;: &quot;NO&quot;,

as a class member; instead store
obj_data: bool = False