rda-python-common 2.1.10__tar.gz → 2.1.11__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-2.1.10/src/rda_python_common.egg-info → rda_python_common-2.1.11}/PKG-INFO +4 -4
  2. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/README.md +1 -1
  3. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/pyproject.toml +3 -3
  4. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_file.py +18 -9
  6. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_log.py +8 -10
  7. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_sig.py +96 -81
  8. {rda_python_common-2.1.10 → rda_python_common-2.1.11/src/rda_python_common.egg-info}/PKG-INFO +4 -4
  9. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/requires.txt +2 -2
  10. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/LICENSE +0 -0
  11. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/setup.cfg +0 -0
  12. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgCMD.py +0 -0
  13. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgDBI.py +0 -0
  14. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgFile.py +0 -0
  15. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgLOG.py +0 -0
  16. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgLock.py +0 -0
  17. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgOPT.py +0 -0
  18. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgSIG.py +0 -0
  19. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgSplit.py +0 -0
  20. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/PgUtil.py +0 -0
  21. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_cmd.py +0 -0
  22. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_dbi.py +0 -0
  23. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_lock.py +0 -0
  24. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_opt.py +0 -0
  25. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_password.py +0 -0
  26. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_split.py +0 -0
  27. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pg_util.py +0 -0
  28. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pgpassword.py +0 -0
  29. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common/pgpassword.usg +0 -0
  30. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  31. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  32. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  33. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-2.1.10 → rda_python_common-2.1.11}/test/test_common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 2.1.10
3
+ Version: 2.1.11
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
@@ -11,8 +11,8 @@ Classifier: Development Status :: 5 - Production/Stable
11
11
  Requires-Python: >=3.7
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: hvac
15
- Requires-Dist: psycopg2==2.9.10
14
+ Requires-Dist: psycopg2-binary
15
+ Requires-Dist: psutil
16
16
  Requires-Dist: rda-python-globus
17
17
  Requires-Dist: unidecode
18
18
  Requires-Dist: hvac
@@ -92,7 +92,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
92
92
  python -c "import rda_python_common; print(rda_python_common.__version__)"
93
93
  ```
94
94
 
95
- You should see the installed version (currently `2.1.10`). If the import
95
+ You should see the installed version (currently `2.1.11`). If the import
96
96
  fails, double-check that the active Python environment is the one where you
97
97
  ran `pip install`.
98
98
 
@@ -72,7 +72,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
72
72
  python -c "import rda_python_common; print(rda_python_common.__version__)"
73
73
  ```
74
74
 
75
- You should see the installed version (currently `2.1.10`). If the import
75
+ You should see the installed version (currently `2.1.11`). If the import
76
76
  fails, double-check that the active Python environment is the one where you
77
77
  ran `pip install`.
78
78
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_common"
7
- version = "2.1.10"
7
+ version = "2.1.11"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -18,8 +18,8 @@ classifiers = [
18
18
  "Development Status :: 5 - Production/Stable",
19
19
  ]
20
20
  dependencies = [
21
- "hvac",
22
- "psycopg2==2.9.10",
21
+ "psycopg2-binary",
22
+ "psutil",
23
23
  "rda-python-globus",
24
24
  "unidecode",
25
25
  "hvac"
@@ -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__ = "2.1.10"
25
+ __version__ = "2.1.11"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -19,6 +19,7 @@ import re
19
19
  import time
20
20
  import glob
21
21
  import json
22
+ import hashlib
22
23
  from .pg_util import PgUtil
23
24
  from .pg_sig import PgSIG
24
25
 
@@ -2874,24 +2875,32 @@ class PgFile(PgUtil, PgSIG):
2874
2875
  (with None for missing files) for multiple files, or None
2875
2876
  on failure.
2876
2877
  """
2877
- cmd = 'md5sum '
2878
2878
  if count > 0:
2879
2879
  checksum = [None]*count
2880
2880
  for i in range(count):
2881
2881
  if op.isfile(file[i]):
2882
- chksm = self.pgsystem(cmd + file[i], logact, 20)
2883
- if chksm:
2884
- ms = re.search(r'(\w{32})', chksm)
2885
- if ms: checksum[i] = ms.group(1)
2882
+ checksum[i] = self._file_md5(file[i], logact)
2886
2883
  else:
2887
2884
  checksum = None
2888
2885
  if op.isfile(file):
2889
- chksm = self.pgsystem(cmd + file, logact, 20)
2890
- if chksm:
2891
- ms = re.search(r'(\w{32})', chksm)
2892
- if ms: checksum = ms.group(1)
2886
+ checksum = self._file_md5(file, logact)
2893
2887
  return checksum
2894
2888
 
2889
+ def _file_md5(self, path, logact=0):
2890
+ """Compute MD5 hex digest of *path*, reading in 1 MiB chunks.
2891
+
2892
+ Returns the hex digest string, or None on read error.
2893
+ """
2894
+ try:
2895
+ h = hashlib.md5()
2896
+ with open(path, 'rb') as fh:
2897
+ for chunk in iter(lambda: fh.read(1048576), b''):
2898
+ h.update(chunk)
2899
+ return h.hexdigest()
2900
+ except OSError as e:
2901
+ self.pglog("Error md5sum {}: {}".format(path, str(e)), logact)
2902
+ return None
2903
+
2895
2904
  # Evaluate md5 checksums and compare them for two given files
2896
2905
  # file1, file2: file names
2897
2906
  # Return: 0 if same and 1 if not
@@ -18,6 +18,7 @@ import grp
18
18
  import shlex
19
19
  import smtplib
20
20
  from email.message import EmailMessage
21
+ import subprocess
21
22
  from subprocess import Popen, PIPE
22
23
  from os import path as op
23
24
  import time
@@ -769,7 +770,7 @@ class PgLOG:
769
770
  nilcnt = 0
770
771
  if begin: sys.stdout.write(line)
771
772
  else:
772
- os.system("more " + usgname)
773
+ subprocess.run(['more', usgname])
773
774
  self.pgexit(0)
774
775
 
775
776
  def err2std(self, line):
@@ -1600,15 +1601,12 @@ class PgLOG:
1600
1601
  os.environ['MAIL'] = re.sub(self.PGLOG['CURUID'], specialist, os.environ['MAIL'])
1601
1602
  home = "{}/{}".format(self.PGLOG['USRHOME'], specialist)
1602
1603
  shell = "tcsh"
1603
- buf = self.pgsystem("grep ^{}: /etc/passwd".format(specialist), self.LOGWRN, 20)
1604
- if buf:
1605
- lines = buf.split('\n')
1606
- for line in lines:
1607
- ms = re.search(r':(/.+):(/.+)', line)
1608
- if ms:
1609
- home = ms.group(1)
1610
- shell = op.basename(ms.group(2))
1611
- break
1604
+ try:
1605
+ pwent = pwd.getpwnam(specialist)
1606
+ home = pwent.pw_dir
1607
+ shell = op.basename(pwent.pw_shell)
1608
+ except KeyError:
1609
+ pass
1612
1610
  if home != os.environ['HOME'] and op.exists(home):
1613
1611
  os.environ['HOME'] = home
1614
1612
  return shell
@@ -14,6 +14,8 @@ import sys
14
14
  import errno
15
15
  import signal
16
16
  import time
17
+ import subprocess
18
+ import psutil
17
19
  from contextlib import contextmanager
18
20
  from .pg_dbi import PgDBI
19
21
 
@@ -204,6 +206,42 @@ class PgSIG(PgDBI):
204
206
  self.PGLOG['LOGMASK'] |= self.MSGLOG # turn on logging before daemon stops
205
207
  self.pglog("{} Started at {}, Stopped gracefully{} by {}".format(self.PGSIG['DSTR'], self.PGSIG['STRTM'], msg, self.current_datetime()), self.LOGWRN)
206
208
 
209
+ # scan running processes via psutil and return a list of dicts matching aname/uname
210
+ # Mirrors the semantics of the previous "ps -u U -f | grep A" / "ps -C A -f" pipelines:
211
+ # - with uname: substring match of aname anywhere in cmdline (looser, like grep)
212
+ # - without uname: aname matches the executable basename (with optional .ext)
213
+ # Each entry has keys: 'pid', 'ppid', 'args' ('args' is cmdline[1:] joined).
214
+ def _scan_app_processes(self, aname, uname=None):
215
+ """Scan running processes matching an application name, optionally by user.
216
+
217
+ Args:
218
+ aname (str): Application name (basename or substring of cmdline).
219
+ uname (str, optional): Username filter.
220
+
221
+ Returns:
222
+ list[dict]: Each dict has keys ``pid``, ``ppid``, ``args``.
223
+ """
224
+ results = []
225
+ for proc in psutil.process_iter(['pid', 'ppid', 'username', 'cmdline']):
226
+ try:
227
+ info = proc.info
228
+ cmdline = info.get('cmdline') or []
229
+ if not cmdline: continue
230
+ if uname is not None and info.get('username') != uname: continue
231
+ if uname:
232
+ if not any(aname in arg for arg in cmdline): continue
233
+ else:
234
+ exe = os.path.basename(cmdline[0])
235
+ if exe != aname and re.sub(r'\.\w+$', '', exe) != aname: continue
236
+ results.append({
237
+ 'pid': info['pid'],
238
+ 'ppid': info['ppid'],
239
+ 'args': ' '.join(cmdline[1:]),
240
+ })
241
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
242
+ continue
243
+ return results
244
+
207
245
  # check if a daemon is running already
208
246
  # aname - application name for the daemon
209
247
  # uname - user login name who started the daemon
@@ -218,21 +256,11 @@ class PgSIG(PgDBI):
218
256
  Returns:
219
257
  int: The process ID of the running daemon, or 0 if not running.
220
258
  """
221
- if uname:
222
- self.check_vuser(uname, aname)
223
- pcmd = "ps -u {} -f | grep {} | grep ' 1 '".format(uname, aname)
224
- mp = r"^\s*{}\s+(\d+)\s+1\s+".format(uname)
225
- else:
226
- pcmd = "ps -C {} -f | grep ' 1 '".format(aname)
227
- mp = r"^\s*\w+\s+(\d+)\s+1\s+"
228
- buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
229
- if buf:
230
- cpid = os.getpid()
231
- lines = buf.split('\n')
232
- for line in lines:
233
- ms = re.match(mp, line)
234
- pid = int(ms.group(1)) if ms else 0
235
- if pid > 0 and pid != cpid: return pid
259
+ if uname: self.check_vuser(uname, aname)
260
+ cpid = os.getpid()
261
+ for p in self._scan_app_processes(aname, uname):
262
+ if p['ppid'] != 1: continue
263
+ if p['pid'] != cpid: return p['pid']
236
264
  return 0
237
265
 
238
266
  # check if an application is running already; other than the current processs
@@ -251,31 +279,21 @@ class PgSIG(PgDBI):
251
279
  Returns:
252
280
  int: The process ID of the running instance, or 0 if not found.
253
281
  """
254
- if uname:
255
- self.check_vuser(uname, aname)
256
- pcmd = "ps -u {} -f | grep {} | grep -v ' grep '".format(uname, aname)
257
- mp = r"^\s*{}\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(uname, aname)
258
- else:
259
- pcmd = "ps -C {} -f".format(aname)
260
- mp = r"^\s*\w+\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(aname)
261
- buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
262
- if not buf: return 0
282
+ if uname: self.check_vuser(uname, aname)
283
+ procs = self._scan_app_processes(aname, uname)
284
+ if not procs: return 0
263
285
  cpids = [os.getpid(), os.getppid()]
264
286
  pids = []
265
287
  ppids = []
266
288
  astrs = []
267
- lines = buf.split('\n')
268
- for line in lines:
269
- ms = re.match(mp, line)
270
- if not ms: continue
271
- pid = int(ms.group(1))
272
- ppid = int(ms.group(2))
289
+ for p in procs:
290
+ pid, ppid = p['pid'], p['ppid']
273
291
  if pid in cpids:
274
292
  if ppid not in cpids: cpids.append(ppid)
275
293
  continue
276
294
  pids.append(pid)
277
295
  ppids.append(ppid)
278
- if sargv: astrs.append(ms.group(3))
296
+ if sargv: astrs.append(p['args'])
279
297
  pcnt = len(pids)
280
298
  if not pcnt: return 0
281
299
  i = 0
@@ -329,25 +347,15 @@ class PgSIG(PgDBI):
329
347
  Returns:
330
348
  int: Number of running instances (excluding the current process).
331
349
  """
332
- if uname:
333
- self.check_vuser(uname, aname)
334
- pcmd = "ps -u {} -f | grep {} | grep -v ' grep '".format(uname, aname)
335
- mp = r"^\s*{}\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(uname, aname)
336
- else:
337
- pcmd = "ps -C {} -f".format(aname)
338
- mp = r"^\s*\w+\s+(\d+)\s+(\d+)\s+.*{}\S*\s+(.*)$".format(aname)
339
- buf = self.pgsystem(pcmd, self.LOGWRN, 20+1024)
340
- if not buf: return 0
350
+ if uname: self.check_vuser(uname, aname)
351
+ procs = self._scan_app_processes(aname, uname)
352
+ if not procs: return 0
341
353
  dpids = [os.getpid(), os.getppid()]
342
354
  pids = []
343
355
  ppids = []
344
356
  astrs = []
345
- lines = buf.split('\n')
346
- for line in lines:
347
- ms = re.match(mp, line)
348
- if not ms: continue
349
- pid = int(ms.group(1))
350
- ppid = int(ms.group(2))
357
+ for p in procs:
358
+ pid, ppid = p['pid'], p['ppid']
351
359
  if pid in dpids:
352
360
  if ppid > 1 and ppid not in dpids: dpids.append(ppid)
353
361
  continue
@@ -356,7 +364,7 @@ class PgSIG(PgDBI):
356
364
  continue
357
365
  pids.append(pid)
358
366
  ppids.append(ppid)
359
- if sargv: astrs.append(ms.group(3))
367
+ if sargv: astrs.append(p['args'])
360
368
  pcnt = len(pids)
361
369
  if not pcnt: return 0
362
370
  i = 0
@@ -627,18 +635,20 @@ class PgSIG(PgDBI):
627
635
  list: PIDs of processes that were successfully killed.
628
636
  """
629
637
  if logact is None: logact = self.LOGWRN
630
- buf = self.pgsystem("ps --ppid {} -o pid".format(pid), logact, 20)
631
638
  pids = []
632
- if buf:
633
- lines = buf.split('\n')
634
- for line in lines:
635
- ms = re.match(r'^\s*(\d+)', line)
636
- if not ms: continue
637
- cid = int(ms.group(1))
638
- if not self.check_process(cid): continue
639
- cids = self.kill_children(cid, logact)
640
- if cids: pids = cids + pids
641
- if self.kill_process(cid, signal.SIGKILL, logact) == self.SUCCESS: pids.insert(0, cid)
639
+ try:
640
+ children = psutil.Process(pid).children()
641
+ except psutil.NoSuchProcess:
642
+ children = []
643
+ except Exception as e:
644
+ self.pglog("Error listing children of pid {}: {}".format(pid, str(e)), logact)
645
+ children = []
646
+ for child in children:
647
+ cid = child.pid
648
+ if not self.check_process(cid): continue
649
+ cids = self.kill_children(cid, logact)
650
+ if cids: pids = cids + pids
651
+ if self.kill_process(cid, signal.SIGKILL, logact) == self.SUCCESS: pids.insert(0, cid)
642
652
  if logact and len(pids): self.pglog("Process({}) Killed".format(','.join(map(str, pids))), logact)
643
653
  return pids
644
654
 
@@ -808,13 +818,11 @@ class PgSIG(PgDBI):
808
818
  Returns:
809
819
  int: 1 if the process is running, 0 otherwise.
810
820
  """
811
- buf = self.pgsystem("ps -p {} -o pid".format(pid), self.LGWNEX, 20)
812
- if buf:
813
- mp = r'^\s*{}$'.format(pid)
814
- lines = buf.split('\n')
815
- for line in lines:
816
- if re.match(mp, line): return 1
817
- return 0
821
+ try:
822
+ os.kill(pid, 0)
823
+ except OSError:
824
+ return 0
825
+ return 1
818
826
 
819
827
  # check a process id on give host
820
828
  def check_host_pid(self, host, pid, pmsg=None, logact=None):
@@ -1092,7 +1100,10 @@ class PgSIG(PgDBI):
1092
1100
  self.PGLOG['ERRFILE'] = re.sub(r'\.log$', '.err', self.PGLOG['LOGFILE'], 1)
1093
1101
  bckcmd += " 2>> {}/{}".format(self.PGLOG['LOGPATH'], self.PGLOG['ERRFILE'])
1094
1102
  bckcmd += " &"
1095
- os.system(bckcmd)
1103
+ # shell=True is required for the redirections (>> / 2>>) and trailing '&';
1104
+ # the '&' makes the shell fork the command and exit, so the command gets
1105
+ # reparented to init (ppid=1), matching the lookup logic in record_background().
1106
+ subprocess.Popen(bckcmd, shell=True)
1096
1107
  return self.record_background(cmd, logact)
1097
1108
 
1098
1109
  # get background process id for given bcmd
@@ -1170,27 +1181,31 @@ class PgSIG(PgDBI):
1170
1181
  """
1171
1182
  if logact is None: logact = self.LOGWRN
1172
1183
  ms = re.match(r'^(\S+)', bcmd)
1173
- if ms:
1174
- aname = ms.group(1)
1175
- else:
1176
- aname = bcmd
1177
- mp = r"^\s*(\S+)\s+(\d+)\s+1\s+.*{}(.*)$".format(aname)
1178
- pc = "ps -u {},{} -f | grep ' 1 ' | grep {}".format(self.PGLOG['CURUID'], self.PGLOG['GDEXUSER'], aname)
1184
+ aname = ms.group(1) if ms else bcmd
1185
+ curuid = self.PGLOG['CURUID']
1186
+ gdexuser = self.PGLOG['GDEXUSER']
1179
1187
  for i in range(2):
1180
- buf = self.pgsystem(pc, logact, 20+1024)
1181
- if buf:
1182
- lines = buf.split('\n')
1183
- for line in lines:
1184
- ms = re.match(mp, line)
1185
- if not ms: continue
1186
- (uid, sbid, acmd) = ms.groups()
1187
- bid = int(sbid)
1188
+ for proc in psutil.process_iter(['pid', 'ppid', 'username', 'cmdline']):
1189
+ try:
1190
+ info = proc.info
1191
+ if info.get('ppid') != 1: continue
1192
+ uid = info.get('username')
1193
+ if uid != curuid and uid != gdexuser: continue
1194
+ cmdline = info.get('cmdline') or []
1195
+ if not cmdline: continue
1196
+ line = ' '.join(cmdline)
1197
+ idx = line.find(aname)
1198
+ if idx < 0: continue
1199
+ bid = info['pid']
1188
1200
  if bid in self.CBIDS: return -1
1189
- if uid == self.PGLOG['GDEXUSER']:
1201
+ acmd = line[idx+len(aname):]
1202
+ if uid == gdexuser:
1190
1203
  acmd = re.sub(r'^\.(pl|py)\s+', '', acmd, 1)
1191
1204
  if re.match(r'^{}{}'.format(aname, acmd), bcmd): continue
1192
1205
  self.CBIDS[bid] = bcmd
1193
1206
  return 1
1207
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
1208
+ continue
1194
1209
  time.sleep(2)
1195
1210
  return 0
1196
1211
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 2.1.10
3
+ Version: 2.1.11
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
@@ -11,8 +11,8 @@ Classifier: Development Status :: 5 - Production/Stable
11
11
  Requires-Python: >=3.7
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: hvac
15
- Requires-Dist: psycopg2==2.9.10
14
+ Requires-Dist: psycopg2-binary
15
+ Requires-Dist: psutil
16
16
  Requires-Dist: rda-python-globus
17
17
  Requires-Dist: unidecode
18
18
  Requires-Dist: hvac
@@ -92,7 +92,7 @@ PgLOG.pglog("hello", PgLOG.LOGWRN)
92
92
  python -c "import rda_python_common; print(rda_python_common.__version__)"
93
93
  ```
94
94
 
95
- You should see the installed version (currently `2.1.10`). If the import
95
+ You should see the installed version (currently `2.1.11`). If the import
96
96
  fails, double-check that the active Python environment is the one where you
97
97
  ran `pip install`.
98
98
 
@@ -1,5 +1,5 @@
1
- hvac
2
- psycopg2==2.9.10
1
+ psycopg2-binary
2
+ psutil
3
3
  rda-python-globus
4
4
  unidecode
5
5
  hvac