rda-python-common 3.0.22__tar.gz → 3.0.24__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {rda_python_common-3.0.22/src/rda_python_common.egg-info → rda_python_common-3.0.24}/PKG-INFO +2 -2
  2. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/README.md +1 -1
  3. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/pyproject.toml +1 -1
  4. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_file.py +15 -2
  6. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_sig.py +46 -0
  7. {rda_python_common-3.0.22 → rda_python_common-3.0.24/src/rda_python_common.egg-info}/PKG-INFO +2 -2
  8. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/LICENSE +0 -0
  9. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/setup.cfg +0 -0
  10. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgCMD.py +0 -0
  11. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgDBI.py +0 -0
  12. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgFile.py +0 -0
  13. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgLOG.py +0 -0
  14. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgLock.py +0 -0
  15. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgOPT.py +0 -0
  16. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgSIG.py +0 -0
  17. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgSplit.py +0 -0
  18. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/PgUtil.py +0 -0
  19. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_cmd.py +0 -0
  20. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_dbi.py +0 -0
  21. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_lock.py +0 -0
  22. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_log.py +0 -0
  23. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_opt.py +0 -0
  24. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_password.py +0 -0
  25. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.22 → rda_python_common-3.0.24}/test/test_common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.22
3
+ Version: 3.0.24
4
4
  Summary: RDA Python common library codes shared by other RDA python packages
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-common
@@ -189,7 +189,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
189
189
  python -c "import rda_python_common; print(rda_python_common.__version__)"
190
190
  ```
191
191
 
192
- You should see the installed version (currently `3.0.22`). If the import
192
+ You should see the installed version (currently `3.0.24`). If the import
193
193
  fails, double-check that the active Python environment is the one where you
194
194
  ran `pip install`.
195
195
 
@@ -165,7 +165,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
165
165
  python -c "import rda_python_common; print(rda_python_common.__version__)"
166
166
  ```
167
167
 
168
- You should see the installed version (currently `3.0.22`). If the import
168
+ You should see the installed version (currently `3.0.24`). If the import
169
169
  fails, double-check that the active Python environment is the one where you
170
170
  ran `pip install`.
171
171
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_common"
7
- version = "3.0.22"
7
+ version = "3.0.24"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -22,7 +22,7 @@ object that existing callers expect.
22
22
 
23
23
  from . import PgLOG, PgUtil, PgDBI, PgFile, PgLock, PgCMD, PgSIG, PgOPT, PgSplit
24
24
 
25
- __version__ = "3.0.22"
25
+ __version__ = "3.0.24"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -878,6 +878,11 @@ class PgFile(PgUtil, PgSIG):
878
878
  objects sharing a key prefix. Each object is downloaded individually and
879
879
  keeps its position relative to the prefix under todir.
880
880
 
881
+ A member already staged under todir at the object's size and no older than
882
+ the object itself is left alone, so a store whose download was cut short
883
+ (by a batch walltime, say) resumes where it stopped instead of starting
884
+ over, and a second copy into the same directory costs nothing.
885
+
881
886
  Args:
882
887
  todir (str): Destination local directory path.
883
888
  fromdir (str): Object key prefix (source path in the bucket).
@@ -890,7 +895,9 @@ class PgFile(PgUtil, PgSIG):
890
895
  if not bucket: bucket = self.PGLOG['OBJCTBKT']
891
896
  ms = re.match(r'^(.+)/$', fromdir)
892
897
  if ms: fromdir = ms.group(1)
893
- flist = self.object_glob(fromdir, bucket, 0, logact)
898
+ # opt 1 adds the LastModified the 'lo' listing already carries; it is only
899
+ # bits 2 and 8 that would cost a metadata call per key
900
+ flist = self.object_glob(fromdir, bucket, 1, logact)
894
901
  if flist == self.FAILURE: return self.FAILURE
895
902
  prefix = fromdir + '/'
896
903
  keys = [key for key in flist if key.startswith(prefix)]
@@ -898,8 +905,14 @@ class PgFile(PgUtil, PgSIG):
898
905
  return self.lmsg(fromdir, "{}-{} to copy to {}".format(self.OHOST, self.PGLOG['MISSFILE'], todir), logact)
899
906
  plen = len(prefix)
900
907
  for key in keys:
908
+ oinfo = flist[key]
901
909
  tofile = "{}/{}".format(todir, key[plen:])
902
- if not self.object_get_local(tofile, key, flist[key]['data_size'], bucket, logact): return self.FAILURE
910
+ linfo = self.check_local_file(tofile, 1, logact)
911
+ # an unparsable LastModified leaves date_modified unset; download again then
912
+ if linfo and 'date_modified' in oinfo and linfo['data_size'] == oinfo['data_size'] and \
913
+ self.cmptime(linfo['date_modified'], linfo['time_modified'],
914
+ oinfo['date_modified'], oinfo['time_modified']) >= 0: continue
915
+ if not self.object_get_local(tofile, key, oinfo['data_size'], bucket, logact): return self.FAILURE
903
916
  return self.SUCCESS
904
917
 
905
918
  # Copy a remote file to object
@@ -63,6 +63,7 @@ class PgSIG(PgDBI):
63
63
  'MTIME': 0, # maximum daemon running time in seconds, 0 for unlimited
64
64
  'STIME': 0, # time the daemon is started
65
65
  'STRTM': '', # string format of 'STIME'
66
+ 'TERMCB': None, # callback to report before a caught termination signal kills the run
66
67
  }
67
68
 
68
69
  # add users for starting this daemon
@@ -480,6 +481,45 @@ class PgSIG(PgDBI):
480
481
  if self.PGSIG['PPID'] <= 1 and len(self.CPIDS) > 0: # passing signal to child processes
481
482
  for pid in self.CPIDS: self.kill_process(pid, signum)
482
483
 
484
+ # catch the termination signal so a batch run gets a chance to report before it dies
485
+ # PBS kills a job that runs out of walltime with SIGTERM first and SIGKILL a few seconds
486
+ # later (the MoM's kill_delay, 10 seconds by default), and the same pair is what qdel
487
+ # sends. SIGTERM is the only warning there is, so trapping it turns the last seconds
488
+ # into a report instead of losing the run silently. only a batch run arms this: on the
489
+ # command line SIGTERM must keep killing the process the way the user expects.
490
+ # callback - function called as callback(signum, frame) to build and send the report
491
+ def catch_term_signal(self, callback):
492
+ """Trap SIGTERM in a batch run so a report can be sent before the job is killed.
493
+
494
+ Does nothing outside a batch run (``PGLOG['CURBID']`` < 1).
495
+
496
+ Args:
497
+ callback: Function called as ``callback(signum, frame)`` from the
498
+ signal handler to build and send the report.
499
+ """
500
+ if self.PGLOG['CURBID'] < 1: return # only a batch run gets killed off a walltime
501
+ self.PGSIG['TERMCB'] = callback
502
+ signal.signal(signal.SIGTERM, self.term_signal_catch)
503
+
504
+ # run the registered callback and then die of the signal that was sent
505
+ def term_signal_catch(self, signum, frame):
506
+ """Report through the registered callback, then re-raise the signal.
507
+
508
+ The default handler is restored before the callback runs so that a
509
+ second signal, or the SIGKILL that follows, ends the run outright
510
+ instead of re-entering here should the callback hang. Re-raising the
511
+ signal keeps the exit status the one the sender asked for.
512
+
513
+ Args:
514
+ signum (int): The signal number received.
515
+ frame: The current stack frame, passed on to the callback.
516
+ """
517
+ signal.signal(signum, signal.SIG_DFL)
518
+ callback = self.PGSIG['TERMCB']
519
+ self.PGSIG['TERMCB'] = None # report only once
520
+ if callback: callback(signum, frame)
521
+ os.kill(os.getpid(), signum)
522
+
483
523
  # wrapper function to call os.kill() logging caught error based on logact
484
524
  # return self.SUCCESS is success; PgLog.FAILURE if not
485
525
  def kill_process(self, pid, signum, logact=0):
@@ -602,6 +642,9 @@ class PgSIG(PgDBI):
602
642
  pid = self.process_fork(msg)
603
643
  if pid == 0: # in child
604
644
  signal.signal(signal.SIGQUIT, self.signal_catch) # catch quit signal only
645
+ if self.PGSIG['TERMCB']: # a child shares the parent's check; it must not report
646
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
647
+ self.PGSIG['TERMCB'] = None
605
648
  self.PGSIG['PPID'] = self.PGSIG['PID']
606
649
  self.PGSIG['PID'] = pid = os.getpid()
607
650
  self.cmdlog("Timeout child to " + msg, time.time(), 0)
@@ -693,6 +736,9 @@ class PgSIG(PgDBI):
693
736
  self.pglog("{}: starts CPID {} for {}".format(self.PGSIG['DSTR'], pid, pname))
694
737
  else:
695
738
  signal.signal(signal.SIGQUIT, signal.SIG_DFL) # turn off catch QUIT signal in child
739
+ if self.PGSIG['TERMCB']: # a child shares the parent's check; it must not report
740
+ signal.signal(signal.SIGTERM, signal.SIG_DFL)
741
+ self.PGSIG['TERMCB'] = None
696
742
  self.PGLOG['LOGMASK'] &= ~self.WARNLG # turn off warn in child
697
743
  self.PGSIG['PPID'] = self.PGSIG['PID']
698
744
  self.PGSIG['PID'] = pid = os.getpid()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.22
3
+ Version: 3.0.24
4
4
  Summary: RDA Python common library codes shared by other RDA python packages
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-common
@@ -189,7 +189,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
189
189
  python -c "import rda_python_common; print(rda_python_common.__version__)"
190
190
  ```
191
191
 
192
- You should see the installed version (currently `3.0.22`). If the import
192
+ You should see the installed version (currently `3.0.24`). If the import
193
193
  fails, double-check that the active Python environment is the one where you
194
194
  ran `pip install`.
195
195