DigitalOcean dynamic DNS update script Inspired by a shell script I saw on Github, I put together a Python version to update the dynamic IP address for a subdomain I have that uses DigitalOcean's nameservers. I added a check to see if the IP address actually needs to be updated, and not do the update if the IP address hasn't changed. All of the user-configurable variables - API token, domain, subdomain - are stored in a separate .env file alongside the script.

#!/usr/bin/env python3

# Import required modules
import dotenv
import json
import os
import requests

# Load user-configured variables from .env file
dotenv.load_dotenv()
token = os.environ.get('DO_API_TOKEN')
domain = os.environ.get('DO_DOMAIN')
subdomain = os.environ.get('DO_SUBDOMAIN')

# Other variables
check_ip_url = 'https://api.ipify.org'
do_api_url = 'https://api.digitalocean.com/v2/domains/'

# Get the current external IP
def get_current_ip():
    curr_ip = requests.get(check_ip_url).text.rstrip()
    return curr_ip

# Get the current subdomain IP
def get_sub_info():
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token
    }
    response = requests.get(do_api_url + domain + '/records', headers=headers).text
    records = json.loads(response)
    for record in records['domain_records']:
        if record['name'] == subdomain:
            subdomain_info = {
                'ip': record['data'],
                'record_id': record['id']
            }
            return subdomain_info

# Update DNS records if required
def update_dns():
    current_ip_address = get_current_ip()
    subdomain_ip_address = get_sub_info()['ip']
    subdomain_record_id = get_sub_info()['record_id']
    if current_ip_address == subdomain_ip_address:
        print('Subdomain DNS record does not need updating.')
        return
    else:
        headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer $token',
        }
        data = '{"data":"' + current_ip_address + '"}'
        response = requests.put(do_api_url + domain + '/records/' + subdomain_record_id, headers=headers, data=data)
        if '200' in response:
            print('Subdomain IP address updated to ' + current_ip_address)
        else:
            print('IP address update failed with message: ' + response.text)
        return

if __name__ == '__main__':
    update_dns()


It works, but isn't the prettiest Python code out there, and I'm sure if could be made a bit more efficient. Are there any stylistic changes I should make to better adhere to best practices? I have run it through a PEP8 checker, and the only thing it brought up was about the line length in a few places, which I'm not super concerned about.
#!/usr/bin/env python3

import os

import dotenv
import requests

dotenv.load_dotenv()
token = os.environ['DO_API_TOKEN']
domain = os.environ['DO_DOMAIN']
subdomain = os.environ['DO_SUBDOMAIN']

records_url = f'https://api.digitalocean.com/v2/domains/{domain}/records/'
session = requests.Session()
session.headers = {
    'Authorization': 'Bearer ' + token
}


def get_current_ip():
    return requests.get('https://api.ipify.org').text.rstrip()


def get_sub_info():
    records = session.get(records_url).json()
    for record in records['domain_records']:
        if record['name'] == subdomain:
            return record


def update_dns():
    current_ip_address = get_current_ip()
    sub_info = get_sub_info()
    subdomain_ip_address = sub_info['data']
    subdomain_record_id = sub_info['id']
    if current_ip_address == subdomain_ip_address:
        print('Subdomain DNS record does not need updating.')
    else:
        response = session.put(records_url + subdomain_record_id, json={'data': current_ip_address})
        if response.ok:
            print('Subdomain IP address updated to ' + current_ip_address)
        else:
            print('IP address update failed with message: ' + response.text)


if __name__ == '__main__':
    update_dns()



The script will not work if any variables are missing, so use [...] instead of .get(...) to throw an error ASAP if needed.
The DO URLs always start with /{domain}/records/ so I included that in the top level constant.
A requests.Session makes multiple requests to the same domain faster as it keeps the connection open, and it lets you specify info like headers once.
A few times you create a variable and immediately return it. You can just return the expression directly.
I felt that the check_ip_url constant didn't add anything, so I inlined it. This is mostly a preference.
Most of the comments do not help readers in any way, so I removed them. But if you want to describe what a function does, use a docstring.
Calling .json() on a response parses the text as JSON for you.
Moving the values of one dict to another dict before finally extracting them just adds another layer, so I just returned record directly.
You called get_sub_info() twice which meant two identical requests. To speed things up, I extracted a variable.
Again, requests makes JSON easy to use, now with the json= argument. This both converts the dictionary to a JSON string and sets the content type. But even without this, you really should have been using json.dumps rather than string concatenation.
response.ok is typically how you check if a request succeeded, or by checking the value of response.status_code. I've never seen anyone using the in operator on a response.
There's no reason to return when a function is ending anyway.