FreeDNS update script I tried to write a script which takes a list of URLs, checks if their FreeDNS record points to the current IP, and updates the record if not.

Intended use is that the script will be run periodically to keep FreeDNS always pointing to the dynamic IP.

Also, a log is kept so the user may later inspect what the script did.

# List of domains that should point to the current IP
freedns_domains = set(["foo.example.com"])

# FreeDNS API endpoint, accessible from https://freedns.afraid.org/api/ (use the ASCII one)
freedns_check_url = "https://freedns.afraid.org/api/?action=getdyndns&amp;sha=REDACTED"

# It is assumed that this address responds with a page containing only the IP address
ip_check_url = "https://ident.me/"


def log(message):
    """
    Appends a string to the logfile (hardcoded as log.txt).
    :param message: The message to be appended. Linebreaks are your responsibility.
    """
    with open("log.txt", "a") as f:
        f.write(message)


import datetime
now = datetime.datetime.now()
log("\n\n" + str(now) + ": Script executing,")
import urllib.request
current_ip = urllib.request.urlopen(ip_check_url).read().decode("utf-8")
log(" IP: " + current_ip)

log("\n\tGetting FreeDNS records...")
import urllib.request
records_raw = urllib.request.urlopen(freedns_check_url).read().decode("utf-8").split("\n")
log(" Found " + str(len(records_raw)) + ".")

for raw_record in records_raw:
    parts = raw_record.split("|")

    domain = parts[0]
    current_freedns_ip = parts[1]
    freedns_update_url = parts[2]

    if domain not in freedns_domains:
        log("\n\t\t" + domain + " is not on the watch list, skipping.")
    else:
        log("\n\t\t" + domain + " (" + current_freedns_ip + ") is on the watch list...")

        if current_ip == current_freedns_ip:
            log(" IPs already match, moving on.")
        else:
            log(" IPs don't match, updating...")
            urllib.request.urlopen(freedns_update_url)
            log(" done.")


log("\n\tScript exited gracefully.")


I know that instead of hard coding freedns_domains, it would be more user-friendly to read them from a text file. I haven't done that since I didn't want to deal with the extra complexity of checking if the input file is full of garbage.

Besides this, in what ways can this script be improved?
Python includes logging in the standard library; rather than write your own, why not use it? You can specify an output file, and handle different levels of logging according to the situation.



Per the style guide, the identifiers of constants like freedns_domains should be UPPERCASE_WITH_UNDERSCORES. Also, any import statements should be at the top of the script.



Your docstring isn't quite formatted correctly, it should look like:

def log(message):
    """Appends a string to the logfile (hardcoded as log.txt).

    :param message: The message to be appended. Linebreaks are your responsibility.

    """


Also, I think that the Google style with sphinx-napoleon is much more readable, but YMMV.



Rather than have virtually the whole script running at the top level of the file, it is conventional to encapsulate it into a single entry point function (sometimes called main) then call it like:

if __name__ == '__main__':
    main()


This makes it much easier to reuse the functionality you've developed by importing it elsewhere without actually running anything.



Rather than using + to concatenate strings, I would generally use str.format, e.g.:

"\n\n" + str(now) + ": Script executing,"


would become:

"\n\n{!s}: Script executing,".format(now)




Improvements are a bit outside the usual scope of CR.SE, but as you've asked:


Yes, reading in the appropriate domains from a file would be a good idea. You could use e.g. a regular expression to validate them, or just try: to access them and deal with any exceptions.
Adding a command line interface (see e.g. argparse) would potentially make this more useful.