rfc: a self-editing script
Steven D'Aprano
steve at REMOVE-THIS-cybersource.com.au
Sat Oct 10 03:58:58 EDT 2009
On Fri, 09 Oct 2009 23:30:16 +0000, gb345 wrote:
> The following fragment is from a tiny maintenance script that, among
> other things, edits itself, by rewriting the line that ends with '###
> REPLACE'.
>
> ######################################################################
>
> import re
> import fileinput
>
> LAST_VERSION = 'VERSION 155' ### REPLACE
>
> service = Service(url='http://url.to.service')
>
> if service.version_string == LAST_VERSION:
> sys.exit(0)
>
> for line in fileinput.input(sys.argv[0], inplace=True):
> if re.search(r"### REPLACE$", line):
> print ("LAST_VERSION = '%s' ### REPLACE" %
> service.version_string)
> else:
> print line,
>
> # ...and goes on to do more stuff...
>
> ######################################################################
>
> This script is meant to run periodically (via cron), and "do more stuff"
> whenever the fetched value of service.version_string differs from what
> it was at the time of the script's prior invocation. (The interval of
> time between such changes of value varies from one change to the next,
> but it is always of the order of several weeks.)
>
> Hence this script needs to preserve state between invocations. The
> rationale for the acrobatics with fileinput above is to make this script
> completely self-contained, by circumventing the need some external means
> (e.g. a second file, or a DB) of preserving state between invocations.
>
> Is there a better way to circumvent the requirement for an external
> repository of state information?
Yes -- change the requirement. What's wrong with having a separate file
to store state?
Self-modifying code is almost always the wrong solution, unless the
problem is "how do I generate an unmaintainable mess?".
But if you absolutely have to write to the program file, then append your
data to the end of the file (as a comment) and later read that, rather
than modifying the actual code in place. That is, you fetch the
LAST_VERSION by reading the last non-empty line in the file, something
like this:
# Untested
def get_last_version(filename):
"""Retrieves the last version number from the given filename,
taken from the last non-empty line."""
candidate = ''
for line in open(filename, 'r'):
line = line.strip()
if line and line.startswith('#'):
candidate = line.lstrip('# \t')
# error checking goes here
return candidate
LAST_VERSION = get_last_version(sys.argv[0])
...
more code goes here
...
# ==================================================
# === Version number history goes here. ===
# === DO NOT insert any code after this point!!! ===
# ==================================================
# 1.0.1
# 1.0.2a
# 1.0.2
# 1.0.5
This has the added advantage that you can track the updates made to the
version number.
--
Steven
More information about the Python-list
mailing list