Skip to content

Commit fd63db9

Browse files
author
michael stack
committed
Make this code run on python3.9 (October, 2020), the default on rockylinux9 and
the os that runs our agents. Should be no functional difference in the changes made below. joshua_model.py was barfing on call to getchildren removed in 3.9 triggered by attempts at changing xml processing. Most of the below changes came via pip install pyupgrade pyupgrade --py39-plus . find . -name "*.py" -exec pyupgrade --py39-plus {} + On changes like the below change: diff --git a/joshua/joshua.py b/joshua/joshua.py index 8065be3..659821d 100755 --- a/joshua/joshua.py +++ b/joshua/joshua.py @@ -41,7 +41,7 @@ def get_username(): def format_ensemble(e, props): return " %-50s %s" % ( e, - " ".join("{}={}".format(k, v) for k, v in sorted(props.items())), + " ".join(f"{k}={v}" for k, v in sorted(props.items())), ) On these sorts of changes: @@ -116,11 +116,11 @@ def start_ensemble( properties["timeout"] = timeout if fail_fast > 0 and not no_fail_fast: - print("Note: Ensemble will complete after {} failed results.".format(fail_fast)) + print(f"Note: Ensemble will complete after {fail_fast} failed results.") properties["fail_fast"] = fail_fast Replaces str.format() with an f-string for a print() statement. Modern, idiomtic python. Nothing wrong w/ using set in the below but pyupgrade thinks the change more 'direct': if not yes and not dryrun: response = input("Do you want to delete these ensembles [y/n]? ") - if response.strip().lower() not in set(["y", "yes"]): + if response.strip().lower() not in {"y", "yes"}: print("Negative response received. Not performing deletion.") return On the below change: -class TimeoutFuture(object): +class TimeoutFuture: def __init__(self, timeout): self.cb_list = [] self.timer = threading.Timer(timeout, self._do_on_ready) In Python 3, classes implicitly inherit from object if no other base class is specified. So, (object) is redundant. On the below, 'r' is default: if env.get('HOSTNAME', False) and os.path.isfile(k8s_namespace_file): pod_name = env['HOSTNAME'] - with open(k8s_namespace_file, 'r') as f: + with open(k8s_namespace_file) as f: namespace = f.read() Below is main change. Element.getchildren() was deprecated in earlier Python versions and removed in Python 3.9. def unwrap_message(text): root = ET.fromstring(text) - return root.getchildren()[0].attrib + return next(iter(root)).attrib On the blow, PEP 585 - Type Hinting Generics In Standard Collections: This PEP, implemented starting in Python 3.9, allows the direct use of built-in collection types (like list, dict, tuple, set, etc.) as generic types in type annotations. -def list_and_watch_active_ensembles(tr) -> Tuple[List[str], fdb.Future]: +def list_and_watch_active_ensembles(tr) -> tuple[list[str], fdb.Future]: return _list_and_watch_ensembles(tr, dir_active, dir_active_changes) On the below, in Python 3 (specifically starting from Python 3.3), IOError became an alias for OSError. This means that IOError is essentially the same as OSError. OSError is now the canonical name for this broad category of OS-related errors (which includes I/O errors). - except IOError: + except OSError: # This is not our process, so we can't open the file. return dict() On the below, PEP 8 Compliance: The unittest module in Python's standard library has adopted snake_case method names (like assertEqual) as the standard, following PEP 8 guidelines. def test_mark_env(self): env = mark_environment(dict()) - self.assertEquals(os.getpid(), int(env[VAR_NAME])) + self.assertEqual(os.getpid(), int(env[VAR_NAME]))
1 parent fe73372 commit fd63db9

File tree

12 files changed

+55
-55
lines changed

12 files changed

+55
-55
lines changed

joshua/joshua.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def get_username():
4141
def format_ensemble(e, props):
4242
return " %-50s %s" % (
4343
e,
44-
" ".join("{}={}".format(k, v) for k, v in sorted(props.items())),
44+
" ".join(f"{k}={v}" for k, v in sorted(props.items())),
4545
)
4646

4747

@@ -75,7 +75,7 @@ def list_active_ensembles(
7575
for props in joshua_model.show_in_progress(e):
7676
print(
7777
"\t{}".format(
78-
" ".join("{}={}".format(k, v) for k, v in sorted(props.items()))
78+
" ".join(f"{k}={v}" for k, v in sorted(props.items()))
7979
)
8080
)
8181

@@ -116,11 +116,11 @@ def start_ensemble(
116116
properties["timeout"] = timeout
117117

118118
if fail_fast > 0 and not no_fail_fast:
119-
print("Note: Ensemble will complete after {} failed results.".format(fail_fast))
119+
print(f"Note: Ensemble will complete after {fail_fast} failed results.")
120120
properties["fail_fast"] = fail_fast
121121

122122
if max_runs > 0 and not no_max_runs:
123-
print("Note: Ensemble will complete after {} runs.".format(max_runs))
123+
print(f"Note: Ensemble will complete after {max_runs} runs.")
124124
properties["max_runs"] = max_runs
125125

126126
if command:
@@ -304,7 +304,7 @@ def _delete_helper(to_delete, yes=False, dryrun=False, sanity=False):
304304

305305
if not yes and not dryrun:
306306
response = input("Do you want to delete these ensembles [y/n]? ")
307-
if response.strip().lower() not in set(["y", "yes"]):
307+
if response.strip().lower() not in {"y", "yes"}:
308308
print("Negative response received. Not performing deletion.")
309309
return
310310

@@ -381,10 +381,10 @@ def download_ensemble(ensemble, out=None, force=False, sanity=False, **args):
381381

382382
if out is None:
383383
out_file = os.path.abspath(
384-
os.path.join(os.getcwd(), "{}.tar.gz".format(ensemble))
384+
os.path.join(os.getcwd(), f"{ensemble}.tar.gz")
385385
)
386386
elif os.path.isdir(out):
387-
out_file = os.path.abspath(os.path.join(out, "{}.tar.gz".format(ensemble)))
387+
out_file = os.path.abspath(os.path.join(out, f"{ensemble}.tar.gz"))
388388
else:
389389
out_file = os.path.abspath(out)
390390
if not os.path.isdir(os.path.dirname(out_file)):
@@ -401,15 +401,15 @@ def download_ensemble(ensemble, out=None, force=False, sanity=False, **args):
401401
return
402402

403403
if not force and os.path.isfile(out_file):
404-
print("File {} already exists. Refusing to overwrite file.".format(out_file))
404+
print(f"File {out_file} already exists. Refusing to overwrite file.")
405405
return
406406

407407
# Treat slash characters as subdirectories
408408
ensemble_dir = os.path.dirname(ensemble)
409409
if ensemble_dir:
410410
os.mkdir(ensemble_dir)
411411

412-
print("Downloading ensemble {} into {}...".format(ensemble, out_file))
412+
print(f"Downloading ensemble {ensemble} into {out_file}...")
413413
with open(out_file, "wb") as fout:
414414
joshua_model.get_ensemble_data(ensemble_id=ensemble, outfile=fout)
415415
print("Download completed")

joshua/joshua_agent.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __repr__(self):
7272

7373

7474
# This is used to handle waiting for a given amount of time.
75-
class TimeoutFuture(object):
75+
class TimeoutFuture:
7676
def __init__(self, timeout):
7777
self.cb_list = []
7878
self.timer = threading.Timer(timeout, self._do_on_ready)
@@ -263,7 +263,7 @@ def tar_artifacts(ensemble, seed, sources, dest, work_dir=None):
263263
)
264264
try:
265265
# Create a temporary directory in the destination where we will store the results.
266-
out_name = "joshua-run-{0}-{1}".format(ensemble, seed)
266+
out_name = f"joshua-run-{ensemble}-{seed}"
267267
tmpdir = os.path.join(work_dir, out_name)
268268
os.makedirs(tmpdir)
269269

@@ -413,7 +413,7 @@ def _cancelled(self):
413413
def run(self, command, cwd, env):
414414
cmd_path = os.path.join(cwd, command[0])
415415
if not os.path.exists(cmd_path):
416-
log("{} doesn't exist".format(cmd_path))
416+
log(f"{cmd_path} doesn't exist")
417417
return
418418
process = subprocess.Popen(
419419
command,
@@ -528,7 +528,7 @@ def _run_ensemble(
528528
k8s_namespace_file = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
529529
if env.get('HOSTNAME', False) and os.path.isfile(k8s_namespace_file):
530530
pod_name = env['HOSTNAME']
531-
with open(k8s_namespace_file, 'r') as f:
531+
with open(k8s_namespace_file) as f:
532532
namespace = f.read()
533533

534534
# Get the cluster config
@@ -554,7 +554,7 @@ def _run_ensemble(
554554
# Set environment variable to use the created temporary directory as its temporary directory.
555555
env["TMP"] = os.path.join(where, "tmp")
556556

557-
log("{} {} {}".format(ensemble, seed, command))
557+
log(f"{ensemble} {seed} {command}")
558558

559559
# Run the test and log output
560560
process = subprocess.Popen(
@@ -577,7 +577,7 @@ def _run_ensemble(
577577
try:
578578
output, _ = process.communicate(timeout=1)
579579
retcode = process.poll()
580-
log("exit code: {}".format(retcode))
580+
log(f"exit code: {retcode}")
581581
# output = output.decode('utf-8')
582582

583583
break
@@ -642,7 +642,7 @@ def _run_ensemble(
642642
try:
643643
i = 0
644644
while os.path.exists(to_write):
645-
to_write = os.path.join(where, "tmp", "console-{0}.log".format(i))
645+
to_write = os.path.join(where, "tmp", f"console-{i}.log")
646646
i += 1
647647

648648
with open(to_write, "wb") as fout:
@@ -808,7 +808,7 @@ def agent(
808808
while True:
809809
# Break if the stop file is defined and present
810810
if stop_file and os.path.exists(stop_file):
811-
log("Exiting due to existing stopfile: {}".format(stop_file))
811+
log(f"Exiting due to existing stopfile: {stop_file}")
812812
break
813813
# Break if requested
814814
if stopAgent():
@@ -885,7 +885,7 @@ def agent(
885885
# Throw away local state for ensembles that are no longer active
886886
local_ensemble_dirs = set(os.listdir(ensemble_dir(basepath=work_dir)))
887887
for e in (local_ensemble_dirs - set(ensembles)) - set(sanity_ensembles):
888-
log("removing {} {}".format(e, ensemble_dir(e, basepath=work_dir)))
888+
log(f"removing {e} {ensemble_dir(e, basepath=work_dir)}")
889889
shutil.rmtree(
890890
ensemble_dir(e, basepath=work_dir), True
891891
) # SOMEDAY: this sometimes throws errors, but we don't know why and it isn't that important
@@ -906,7 +906,7 @@ def agent(
906906
try:
907907
watch.wait_for_any(watch, sanity_watch, TimeoutFuture(1.0))
908908
except Exception as e:
909-
log("watch error: {}".format(e))
909+
log(f"watch error: {e}")
910910
watch = None
911911
time.sleep(1.0)
912912

joshua/joshua_model.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def is_message(text):
183183

184184
def unwrap_message(text):
185185
root = ET.fromstring(text)
186-
return root.getchildren()[0].attrib
186+
return next(iter(root)).attrib
187187

188188

189189
def load_datetime(string):
@@ -224,7 +224,7 @@ def identify_existing_ensembles(tr, ensembles):
224224

225225

226226
@transactional
227-
def list_and_watch_active_ensembles(tr) -> Tuple[List[str], fdb.Future]:
227+
def list_and_watch_active_ensembles(tr) -> tuple[list[str], fdb.Future]:
228228
return _list_and_watch_ensembles(tr, dir_active, dir_active_changes)
229229

230230

@@ -258,7 +258,7 @@ def _unpack_property(ensemble, key, value, into):
258258
into[t[1]] = struct.unpack("<Q", value)[0]
259259

260260

261-
def _list_ensembles(tr, dir) -> List[Tuple[str, Dict]]:
261+
def _list_ensembles(tr, dir) -> list[tuple[str, dict]]:
262262
prop_reads = []
263263
for k, v in tr[dir.range()]:
264264
(ensemble,) = dir.unpack(k)
@@ -281,17 +281,17 @@ def _list_ensembles(tr, dir) -> List[Tuple[str, Dict]]:
281281

282282

283283
@transactional
284-
def list_active_ensembles(tr) -> List[Tuple[str, Dict]]:
284+
def list_active_ensembles(tr) -> list[tuple[str, dict]]:
285285
return _list_ensembles(tr, dir_active)
286286

287287

288288
@transactional
289-
def list_sanity_ensembles(tr) -> List[Tuple[str, Dict]]:
289+
def list_sanity_ensembles(tr) -> list[tuple[str, dict]]:
290290
return _list_ensembles(tr, dir_sanity)
291291

292292

293-
def list_all_ensembles() -> List[Tuple[str, Dict]]:
294-
ensembles: List[Tuple[str, Dict]] = []
293+
def list_all_ensembles() -> list[tuple[str, dict]]:
294+
ensembles: list[tuple[str, dict]] = []
295295
r = dir_all_ensembles.range()
296296
start = r.start
297297
tr = db.create_transaction()
@@ -405,7 +405,7 @@ def _create_ensemble(tr, ensemble_id, properties, sanity=False):
405405
dir, changes = get_dir_changes(sanity)
406406

407407
if tr[dir_all_ensembles[ensemble_id]] != None:
408-
print("{} already inserted".format(ensemble_id))
408+
print(f"{ensemble_id} already inserted")
409409
return # Already inserted
410410
tr[dir_all_ensembles[ensemble_id]] = b""
411411
for k, v in properties.items():
@@ -458,7 +458,7 @@ def stop_user_ensembles(username, sanity=False):
458458

459459
def get_active_ensembles(
460460
stopped, sanity=False, username=None
461-
) -> List[Tuple[str, Dict]]:
461+
) -> list[tuple[str, dict]]:
462462
if stopped:
463463
ensemble_list = list_all_ensembles()
464464
elif sanity:
@@ -655,7 +655,7 @@ def _get_snap_counter(tr: fdb.Transaction, ensemble_id: str, counter: str) -> in
655655

656656
def _get_seeds_and_heartbeats(
657657
ensemble_id: str, tr: fdb.Transaction
658-
) -> List[Tuple[int, float]]:
658+
) -> list[tuple[int, float]]:
659659
result = []
660660
for k, v in tr.snapshot[dir_ensemble_incomplete[ensemble_id]["heartbeat"].range()]:
661661
(seed,) = dir_ensemble_incomplete[ensemble_id]["heartbeat"].unpack(k)
@@ -718,7 +718,7 @@ def should_run_ensemble(tr: fdb.Transaction, ensemble_id: str) -> bool:
718718

719719

720720
@transactional
721-
def show_in_progress(tr: fdb.Transaction, ensemble_id: str) -> List[Tuple[int, Dict]]:
721+
def show_in_progress(tr: fdb.Transaction, ensemble_id: str) -> list[tuple[int, dict]]:
722722
"""
723723
Returns a list of properties for in progress tests
724724
"""

joshua/process_handling.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def get_environment(pid):
107107
var_strs,
108108
)
109109
)
110-
except IOError:
110+
except OSError:
111111
# This is not our process, so we can't open the file.
112112
return dict()
113113

@@ -238,7 +238,7 @@ def test_check_alive(self):
238238

239239
def test_mark_env(self):
240240
env = mark_environment(dict())
241-
self.assertEquals(os.getpid(), int(env[VAR_NAME]))
241+
self.assertEqual(os.getpid(), int(env[VAR_NAME]))
242242

243243
def test_get_all_pids(self):
244244
if sys.platform != "linux2":
@@ -268,9 +268,9 @@ def test_get_environment(self):
268268
# Make sure the environment for this process is the same
269269
# as we know it to be.
270270
env = get_environment(str(os.getpid()))
271-
self.assertEquals(env, os.environ)
271+
self.assertEqual(env, os.environ)
272272
env = get_environment(os.getpid())
273-
self.assertEquals(env, os.environ)
273+
self.assertEqual(env, os.environ)
274274

275275
def test_retrieve_children(self):
276276
if sys.platform != "linux2":
@@ -285,7 +285,7 @@ def test_retrieve_children(self):
285285
stderr=subprocess.PIPE,
286286
)
287287
pids = retrieve_children()
288-
self.assertEquals(len(pids), 10)
288+
self.assertEqual(len(pids), 10)
289289

290290
def test_kill_all_children(self):
291291
if sys.platform != "linux2":
@@ -300,7 +300,7 @@ def test_kill_all_children(self):
300300
stderr=subprocess.PIPE,
301301
)
302302
self.assertTrue(kill_all_children())
303-
self.assertEquals(len(retrieve_children()), 0)
303+
self.assertEqual(len(retrieve_children()), 0)
304304

305305
def test_wait_for_death(self):
306306
process = subprocess.Popen(

joshua_done_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import List
1111

1212
@fdb.transactional
13-
def write_ensemble_data(tr, path: List[str]):
13+
def write_ensemble_data(tr, path: list[str]):
1414
root_dir = fdb.directory.create_or_open(tr, tuple(path))
1515
num = os.getenv('JOSHUA_SEED', None)
1616
has_joshua_seed = num is not None
@@ -28,8 +28,8 @@ def write_ensemble_data(tr, path: List[str]):
2828
parser.add_argument('ensemble_dir')
2929
parser.add_argument('cluster_file')
3030
args = parser.parse_args()
31-
dirPath: List[str] = args.ensemble_dir.split(',')
31+
dirPath: list[str] = args.ensemble_dir.split(',')
3232
print('dirPath = ({})'.format(",".join(dirPath)))
33-
print('clusterFile = {}'.format(args.cluster_file))
33+
print(f'clusterFile = {args.cluster_file}')
3434
db = fdb.open(args.cluster_file if args.cluster_file != 'None' else None)
3535
write_ensemble_data(db, dirPath)

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[bdist_wheel]
2-
python-tag = py38
2+
python-tag = py39

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
+ module.platforms
7171
+ [
7272
"Programming Language :: Python :: 3",
73-
"Programming Language :: Python :: 3.8",
73+
"Programming Language :: Python :: 3.9",
7474
"Programming Language :: Python :: Implementation :: CPython",
7575
],
7676
)

test_joshua_model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def with_script(tmp_path, script_contents):
5050
return factory
5151

5252
def add_bash_script(self, name, contents):
53-
script = "#!/bin/bash\n{}".format(contents).encode("utf-8")
53+
script = f"#!/bin/bash\n{contents}".encode("utf-8")
5454
entry = tarfile.TarInfo(name)
5555
entry.mode = 0o755
5656
entry.size = len(script)
@@ -119,9 +119,9 @@ def fdb_cluster():
119119
port = getFreePort()
120120
cluster_file = os.path.join(tmp_dir, "fdb.cluster")
121121
with open(cluster_file, "w") as f:
122-
f.write("abdcefg:[email protected]:{}".format(port))
122+
f.write(f"abdcefg:[email protected]:{port}")
123123
proc = subprocess.Popen(
124-
["fdbserver", "-p", "auto:{}".format(port), "-C", cluster_file], cwd=tmp_dir
124+
["fdbserver", "-p", f"auto:{port}", "-C", cluster_file], cwd=tmp_dir
125125
)
126126

127127
subprocess.check_output(

webapp/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
load_dotenv(os.path.join(basedir, '.env'))
2626

2727

28-
class Config(object):
28+
class Config:
2929
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
3030
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
3131
'sqlite:///' + os.path.join(basedir, 'db.sqlite')

webapp/joshua_webapp/api.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ def upload_ensemble():
8787
try:
8888
properties = schema.load(request_data)
8989
except Exception as err:
90-
app.logger.info('api_upload: Validation error: {}'.format(err))
90+
app.logger.info(f'api_upload: Validation error: {err}')
9191
return {
92-
"message": 'api_upload: Post field validation error: {}'.format(err)
92+
"message": f'api_upload: Post field validation error: {err}'
9393
}, 422
9494
fileobj = request_files['file']
9595
filename = secure_filename(fileobj.filename)
@@ -102,7 +102,7 @@ def upload_ensemble():
102102
if not tarfile.is_tarfile(saved_file):
103103
os.remove(saved_file)
104104
app.logger.info(
105-
'api_upload: not a valid tar file: {}'.format(saved_file))
105+
f'api_upload: not a valid tar file: {saved_file}')
106106
return {"error": 'api_upload: not a valid tar file'}, 400
107107

108108
# convert to non-unicode string for username
@@ -128,7 +128,7 @@ def stop_ensemble(ensemble):
128128
properties = joshua_model.get_ensemble_properties(ensemble)
129129
sanity = properties[
130130
'sanity'] if properties and 'sanity' in properties else True
131-
app.logger.info('Stop ensemble {} {}'.format(ensemble, sanity))
131+
app.logger.info(f'Stop ensemble {ensemble} {sanity}')
132132
joshua_model.stop_ensemble(ensemble, sanity=sanity)
133133
return jsonify('OK'), 200
134134

@@ -139,6 +139,6 @@ def resume_ensemble(ensemble):
139139
properties = joshua_model.get_ensemble_properties(ensemble)
140140
sanity = properties[
141141
'sanity'] if properties and 'sanity' in properties else True
142-
app.logger.info('Resume ensemble {} {}'.format(ensemble, sanity))
142+
app.logger.info(f'Resume ensemble {ensemble} {sanity}')
143143
joshua_model.resume_ensemble(ensemble, sanity=sanity)
144144
return jsonify('OK'), 200

0 commit comments

Comments
 (0)