rfc: a self-editing script

G

gb345

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?

G
 
S

Steven D'Aprano

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.
 
G

garabik-news-2005-05

Steven D'Aprano said:
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:
....

# ==================================================
# === 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.

And my experience taught me that is pays off to include also date (and
time) of each change, preferably in the RFC 3339 format. Priceless when
you hunt for an unnoticed bug or a crash that happened sometime in the
past...

e.g.:

# 1.0.1 2006-08-07 12:34:56-06:00
# 1.0.2a 2006-08-08 13:35:57-07:00
# 1.0.2 2006-08-10 01:04:56-06:00

etc...

--
-----------------------------------------------------------
| Radovan Garabík http://kassiopeia.juls.savba.sk/~garabik/ |
| __..--^^^--..__ garabik @ kassiopeia.juls.savba.sk |
-----------------------------------------------------------
Antivirus alert: file .signature infected by signature virus.
Hi! I'm a signature virus! Copy me into your signature file to help me spread!
 
G

gb345

In said:
But if you absolutely have to write to the program file...

No, don't have to, beyond the urge to satisfy a very idiosyncratic
aesthetic imperative...
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.


Thanks, these are great ideas. Just the feedback I was looking for.

G.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top