rda-python-common 2.1.7__py3-none-any.whl → 2.1.9__py3-none-any.whl

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.
@@ -0,0 +1,38 @@
1
+ """rda_python_common: shared utility package for RDA Python tools.
2
+
3
+ This package exposes two parallel APIs:
4
+
5
+ 1. Legacy module-based API (back-compat). Import the capitalized submodules
6
+ and call their module-level functions, e.g.::
7
+
8
+ from rda_python_common import PgLOG
9
+ PgLOG.pglog("message", PgLOG.LOGWRN)
10
+
11
+ 2. Class-based API (preferred for new code). Import the class from the
12
+ lower-case module and either instantiate or subclass it, e.g.::
13
+
14
+ from rda_python_common.pg_log import PgLOG
15
+ log = PgLOG()
16
+ log.pglog("message", log.LOGWRN)
17
+
18
+ The legacy submodules are eagerly imported below so that
19
+ ``from rda_python_common import PgLOG`` continues to return the module
20
+ object that existing callers expect.
21
+ """
22
+
23
+ from . import PgLOG, PgUtil, PgDBI, PgFile, PgLock, PgCMD, PgSIG, PgOPT, PgSplit
24
+
25
+ __version__ = "2.1.9"
26
+
27
+ __all__ = [
28
+ "PgLOG",
29
+ "PgUtil",
30
+ "PgDBI",
31
+ "PgFile",
32
+ "PgLock",
33
+ "PgCMD",
34
+ "PgSIG",
35
+ "PgOPT",
36
+ "PgSplit",
37
+ "__version__",
38
+ ]
@@ -62,12 +62,12 @@ class PgDBI(PgLOG):
62
62
  super().__init__() # initialize parent class
63
63
 
64
64
  # PostgreSQL specified query timestamp format
65
- self.fmtyr = lambda fn=self: "extract(year from {})::int".format(fn)
66
- self.fmtqt = lambda fn=self: "extract(quarter from {})::int".format(fn)
67
- self.fmtmn = lambda fn=self: "extract(month from {})::int".format(fn)
68
- self.fmtdt = lambda fn=self: "date({})".format(fn)
69
- self.fmtym = lambda fn=self: "to_char({}, 'yyyy-mm')".format(fn)
70
- self.fmthr = lambda fn=self: "extract(hour from {})::int".format(fn)
65
+ self.fmtyr = lambda fn: "extract(year from {})::int".format(fn)
66
+ self.fmtqt = lambda fn: "extract(quarter from {})::int".format(fn)
67
+ self.fmtmn = lambda fn: "extract(month from {})::int".format(fn)
68
+ self.fmtdt = lambda fn: "date({})".format(fn)
69
+ self.fmtym = lambda fn: "to_char({}, 'yyyy-mm')".format(fn)
70
+ self.fmthr = lambda fn: "extract(hour from {})::int".format(fn)
71
71
 
72
72
  self.pgdb = None # reference to a connected database object
73
73
  self.curtran = 0 # 0 - no transaction, 1 - in transaction
@@ -577,7 +577,7 @@ class PgDBI(PgLOG):
577
577
  self.qelog(dberror, 0, "Retry Connecting", ary, pgcnt, self.LOGWRN)
578
578
  self.pgconnect(1, pgcnt + 1)
579
579
  return (self.FAILURE if not self.pgdb else self.SUCCESS)
580
- elif re.match(r'^55', pgcode): # try to lock again
580
+ elif pgcode.startswith('55'): # try to lock again
581
581
  self.qelog(dberror, 10, "Retry Locking", ary, pgcnt, self.LOGWRN)
582
582
  return self.SUCCESS
583
583
  elif pgcode == '25P02': # try to add table
@@ -2595,7 +2595,7 @@ class PgFile(PgUtil, PgSIG):
2595
2595
  if opt&17:
2596
2596
  dy = int(items[6])
2597
2597
  mn = self.get_month(items[5])
2598
- if re.match(r'^\d+$', items[7]):
2598
+ if items[7].isdigit():
2599
2599
  yr = int(items[7])
2600
2600
  mtime = "00:00:00"
2601
2601
  else:
@@ -2972,7 +2972,7 @@ class PgFile(PgUtil, PgSIG):
2972
2972
  if dir is None:
2973
2973
  if isinstance(val, int):
2974
2974
  self.DIRLVLS = val
2975
- elif re.match(r'^\d+$', val):
2975
+ elif val.isdigit():
2976
2976
  self.DIRLVLS = int(val)
2977
2977
  elif dir and not re.match(r'^(\.|\./|/)$', dir) and dir not in self.DELDIRS:
2978
2978
  self.DELDIRS[dir] = val
@@ -1,7 +1,7 @@
1
1
  ###############################################################################
2
2
  # Title: pg_lock.py
3
3
  # Author: Zaihua Ji, zji@ucar.edu
4
- # Date: 08/118/2020
4
+ # Date: 08/18/2020
5
5
  # 2025-01-10 transferred to package rda_python_common from
6
6
  # https://github.com/NCAR/rda-shared-libraries.git
7
7
  # 2025-12-01 convert to class PgLock
@@ -252,7 +252,7 @@ class PgLOG:
252
252
  msg = self.PGLOG['PRGMSG'] + "\n" + msg
253
253
  self.PGLOG['PRGMSG'] = ""
254
254
  if self.PGLOG['ERRCNT'] == 0:
255
- if not re.search(r'\n$', msg): msg += "!\n"
255
+ if not msg.endswith('\n'): msg += "!\n"
256
256
  else:
257
257
  if self.PGLOG['ERRCNT'] == 1:
258
258
  msg += " with 1 Error:\n"
@@ -1423,7 +1423,7 @@ class PgLOG:
1423
1423
  try:
1424
1424
  self.PGLOG['RDAUID'] = self.PGLOG['GDEXUID'] = pwd.getpwnam(self.PGLOG['GDEXUSER']).pw_uid
1425
1425
  self.PGLOG['RDAGID'] = self.PGLOG['GDEXGID'] = grp.getgrnam(self.PGLOG['GDEXGRP']).gr_gid
1426
- except:
1426
+ except KeyError:
1427
1427
  self.PGLOG['RDAUID'] = self.PGLOG['GDEXUID'] = 0
1428
1428
  self.PGLOG['RDAGID'] = self.PGLOG['GDEXGID'] = 0
1429
1429
  if self.PGLOG['CURUID'] == self.PGLOG['GDEXUSER']: self.PGLOG['SETUID'] = self.PGLOG['GDEXUSER']
@@ -1624,8 +1624,8 @@ class PgLOG:
1624
1624
  missthen = 0
1625
1625
  try:
1626
1626
  rf = open(resource, 'r')
1627
- except:
1628
- return # skip if cannot open
1627
+ except OSError:
1628
+ return # skip if cannot open
1629
1629
  nline = rf.readline()
1630
1630
  while nline:
1631
1631
  line = self.pgtrim(nline)
@@ -1638,12 +1638,12 @@ class PgLOG:
1638
1638
  missthen = 0
1639
1639
  if re.match(r'^then$', line): continue # then on next line
1640
1640
  checkif = 0 # end of inline if
1641
- elif re.match(r'^endif', line):
1641
+ elif line.startswith('endif'):
1642
1642
  checkif = 0 # end of if
1643
1643
  continue
1644
1644
  elif checkif == -1: # skip the line
1645
1645
  continue
1646
- elif checkif == 2 and re.match(r'^else', line):
1646
+ elif checkif == 2 and line.startswith('else'):
1647
1647
  checkif = -1 # done check envs in if
1648
1648
  continue
1649
1649
  if checkif == 1:
@@ -1158,7 +1158,7 @@ class PgOPT(PgFile):
1158
1158
  if self.OPTS[opt][2]&16:
1159
1159
  if not val:
1160
1160
  val = 0
1161
- elif re.match(r'^\d+$', val):
1161
+ elif val.isdigit():
1162
1162
  val = int(val)
1163
1163
  elif val and (opt == 'DS' or opt == 'OD'):
1164
1164
  val = self.format_dataset_id(val)
@@ -961,7 +961,7 @@ class PgSIG(PgDBI):
961
961
  lines = buf.split('\n')
962
962
  for line in lines:
963
963
  if chkt:
964
- if re.match(r'^Job', line):
964
+ if line.startswith('Job'):
965
965
  line = re.sub(r'^Job ID', 'JobID', line, 1)
966
966
  line = re.sub(r'Finish Time', 'FinishTime', line, 1)
967
967
  line = re.sub(r'Req Mem', 'ReqMem', line, 1)
@@ -1,7 +1,7 @@
1
1
  ###############################################################################
2
2
  # Title: pg_split.py -- PostgreSQL DataBase Interface foe table wfile
3
3
  # Author: Zaihua Ji, zji@ucar.edu
4
- # Date: 09/010/2024
4
+ # Date: 09/10/2024
5
5
  # 2025-01-10 transferred to package rda_python_common from
6
6
  # https://github.com/NCAR/rda-shared-libraries.git
7
7
  # 2025-12-01 convert to class PgSplit
@@ -12,8 +12,9 @@ import os
12
12
  import re
13
13
  from os import path as op
14
14
  from .pg_util import PgUtil
15
+ from .pg_dbi import PgDBI
15
16
 
16
- class PgSplit(PgUtil):
17
+ class PgSplit(PgUtil, PgDBI):
17
18
  """Manages synchronisation of wfile records between shared and per-dataset tables.
18
19
 
19
20
  Handles compare, add, update, and delete operations between the shared
@@ -93,22 +93,22 @@ class PgUtil(PgLOG):
93
93
  int | str: Numeric month (1-12) when fmt is None; formatted string otherwise.
94
94
  """
95
95
  if not isinstance(mn, int):
96
- if re.match(r'^\d+$', mn):
96
+ if mn.isdigit():
97
97
  mn = int(mn)
98
98
  else:
99
99
  for m in range(12):
100
100
  if re.match(mn, self.MONTHS[m], re.I):
101
101
  mn = m + 1
102
102
  break
103
- if fmt and mn > 0 and mn < 13:
103
+ if fmt and 0 < mn < 13:
104
104
  slen = len(fmt)
105
105
  if slen == 2:
106
106
  smn = "{:02}".format(mn)
107
- elif re.match(r'^mon', fmt, re.I):
107
+ elif fmt[:3].lower() == 'mon':
108
108
  smn = self.MNS[mn-1] if slen == 3 else self.MONTHS[mn-1]
109
- if re.match(r'^Mon', fmt):
109
+ if fmt.startswith('Mon'):
110
110
  smn = smn.capitalize()
111
- elif re.match(r'^MON', fmt):
111
+ elif fmt.startswith('MON'):
112
112
  smn = smn.upper()
113
113
  else:
114
114
  smn = str(mn)
@@ -131,26 +131,26 @@ class PgUtil(PgLOG):
131
131
  formatted string otherwise.
132
132
  """
133
133
  if not isinstance(wday, int):
134
- if re.match(r'^\d+$', wday):
134
+ if wday.isdigit():
135
135
  wday = int(wday)
136
136
  else:
137
137
  for w in range(7):
138
138
  if re.match(wday, self.WDAYS[w], re.I):
139
139
  wday = w
140
140
  break
141
- if fmt and wday >= 0 and wday <= 6:
141
+ if fmt and 0 <= wday <= 6:
142
142
  slen = len(fmt)
143
143
  if slen == 4:
144
144
  swday = self.WDAYS[wday]
145
- if re.match(r'^We', fmt):
145
+ if fmt.startswith('We'):
146
146
  swday = swday.capitalize()
147
- elif re.match(r'^WE', fmt):
147
+ elif fmt.startswith('WE'):
148
148
  swday = swday.upper()
149
149
  elif slen == 3:
150
150
  swday = self.WDS[wday]
151
- if re.match(r'^Ww', fmt):
151
+ if fmt.startswith('Ww'):
152
152
  swday = swday.capitalize()
153
- elif re.match(r'^WW', fmt):
153
+ elif fmt.startswith('WW'):
154
154
  swday = swday.upper()
155
155
  else:
156
156
  swday = str(wday)
@@ -179,7 +179,7 @@ class PgUtil(PgLOG):
179
179
  if exists is None or exists:
180
180
  if not op.exists(file): return '' # file does not exist
181
181
  bname = op.basename(file)
182
- if re.match(r'^,.*', bname): return '' # hidden file
182
+ if bname.startswith(','): return '' # hidden file
183
183
  if re.search(r'index\.(htm|html|shtml)$', bname, re.I): return '' # index file
184
184
  if type and type != 'D': return type
185
185
  if re.search(r'\.(doc|php|html|shtml)(\.|$)', bname, re.I): return '' # file with special extention
@@ -314,7 +314,7 @@ class PgUtil(PgLOG):
314
314
  """
315
315
  if not date: return default
316
316
  if not isinstance(date, str): date = str(date)
317
- if re.match(r'^0000', date): return default
317
+ if date.startswith('0000'): return default
318
318
  return date
319
319
 
320
320
  # fmt: date format, default to "YYYY-MM-DD"
@@ -422,7 +422,7 @@ class PgUtil(PgLOG):
422
422
  adt = re.split(sep, sdt)
423
423
  acnt = len(adt)
424
424
  for i in range(acnt):
425
- if re.match(r'^\d+$', adt[i]): adt[i] = int(adt[i])
425
+ if adt[i].isdigit(): adt[i] = int(adt[i])
426
426
  return adt
427
427
 
428
428
  # date: given date in format of fromfmt
@@ -490,19 +490,20 @@ class PgUtil(PgLOG):
490
490
  if i >= mcnt: break
491
491
  fmt = formats[k]
492
492
  val = ms[0][i]
493
- if re.match(r'^Y', fmt, re.I):
493
+ head = fmt[:1].upper()
494
+ if head == 'Y':
494
495
  dates[0] = int(val)
495
496
  if len(fmt) == 3: dates[0] *= 10
496
- elif re.match(r'^C', fmt, re.I):
497
+ elif head == 'C':
497
498
  dates[0] = 100 * int(val) # year at end of century
498
- elif re.match(r'^M', fmt, re.I):
499
- if re.match(r'^Mon', fmt, re.I):
499
+ elif head == 'M':
500
+ if fmt[:3].upper() == 'MON':
500
501
  dates[1] = self.get_month(val)
501
502
  else:
502
503
  dates[1] = int(val)
503
- elif re.match(r'^Q', fmt, re.I):
504
+ elif head == 'Q':
504
505
  dates[1] = 3 * int(val) # month at end of quarter
505
- elif re.match(r'^H', fmt, re.I): # hour
506
+ elif head == 'H': # hour
506
507
  dates.append(int(val))
507
508
  else: # day
508
509
  dates[2] = int(val)
@@ -653,11 +654,11 @@ class PgUtil(PgLOG):
653
654
  slen = len(fmt)
654
655
  if slen == 2:
655
656
  smn = "{:02}".format(m)
656
- elif re.match(r'^mon', fmt, re.I):
657
+ elif fmt[:3].lower() == 'mon':
657
658
  smn = self.MNS[m-1] if slen == 3 else self.MONTHS[m-1]
658
- if re.match(r'^Mo', fmt):
659
+ if fmt.startswith('Mo'):
659
660
  smn = smn.capitalize()
660
- elif re.match(r'^MO', fmt):
661
+ elif fmt.startswith('MO'):
661
662
  smn = smn.upper()
662
663
  else:
663
664
  smn = str(m)
@@ -4,17 +4,42 @@
4
4
  # Author: Zaihua Ji, zji@ucar.edu
5
5
  # Date: 2025-10-27
6
6
  # 2025-12-02 convert to class PgPassword
7
- # Purpose: python script to retrieve passwords for postgrsql login to connect a
7
+ # Purpose: python script to retrieve passwords for postgresql login to connect a
8
8
  # gdex database from inside an python application
9
9
  # Github: https://github.com/NCAR/rda-python-common.git
10
10
  ##################################################################################
11
+ """
12
+ pgpassword.py - Command-line helper that retrieves a PostgreSQL password.
13
+
14
+ Provides the PgPassword class and a ``main`` entry point used by the
15
+ ``pgpassword`` console script. The password is looked up first from
16
+ OpenBao (using the URL/token configured in PgDBI) and, if not found,
17
+ from the user's ``.pgpass`` file. The result is printed to stdout so
18
+ that shell wrappers and other RDA utilities can capture it.
19
+ """
11
20
  import sys
12
21
  import re
13
22
  from .pg_dbi import PgDBI
14
23
 
15
24
  class PgPassword(PgDBI):
25
+ """
26
+ Command-line helper for retrieving a PostgreSQL login password.
27
+
28
+ Inherits from PgDBI to reuse its database-connection metadata
29
+ (PGDBI), default schema handling, and password-lookup methods
30
+ (``get_baopassword`` / ``get_pgpassword``).
31
+
32
+ Instance attributes set in __init__:
33
+ DBFLDS -- mapping of CLI option letters to PGDBI field names
34
+ DBINFO -- per-invocation overrides for dbname/scname/lnname/
35
+ dbhost/dbport supplied via CLI options
36
+ dbopt -- True once at least one DB-override option is seen,
37
+ triggering ``default_scinfo`` before lookup
38
+ password -- the retrieved password (set by ``start_actions``)
39
+ """
16
40
 
17
41
  def __init__(self):
42
+ """Initialize PgPassword with empty DB-override info and option maps."""
18
43
  super().__init__() # initialize parent class
19
44
  self.DBFLDS = {
20
45
  'd': 'dbname',
@@ -34,12 +59,34 @@ class PgPassword(PgDBI):
34
59
  self.password = ''
35
60
 
36
61
  # read in command line parameters
37
- def read_parameters(self):
62
+ def read_parameters(self):
63
+ """
64
+ Parse ``sys.argv`` and apply CLI overrides.
65
+
66
+ Recognized options:
67
+ -? / --help -- show usage and exit
68
+ -l URL -- OpenBao URL (stored in self.PGDBI['BAOURL'])
69
+ -k TOKEN -- OpenBao token name (stored in self.PGDBI['BAOTOKEN'])
70
+ -d NAME -- PostgreSQL database name
71
+ -c NAME -- PostgreSQL schema name
72
+ -u NAME -- PostgreSQL login user name
73
+ -h HOST -- PostgreSQL server host name
74
+ -p PORT -- PostgreSQL port number
75
+
76
+ With no arguments, all defaults inherited from PgDBI/PgLOG are
77
+ used and the password lookup proceeds. Unknown options, stray
78
+ values, or an option without its required value cause an
79
+ immediate error exit via ``self.pglog(..., LGEREX)``.
80
+ """
38
81
  argv = sys.argv[1:]
39
82
  opt = None
40
- dohelp = True
41
83
  for arg in argv:
42
- if re.match(r'^-\w+$', arg):
84
+ if arg in ('-?', '-help', '--help'):
85
+ self.set_help_path(__file__)
86
+ self.show_usage("pgpassword")
87
+ elif re.match(r'^-[a-zA-Z]$', arg):
88
+ if opt:
89
+ self.pglog("-" + opt + ": missing option value", self.LGEREX)
43
90
  opt = arg[1:]
44
91
  elif opt:
45
92
  if opt == 'l':
@@ -50,32 +97,31 @@ class PgPassword(PgDBI):
50
97
  self.dbopt = True
51
98
  self.DBINFO[self.DBFLDS[opt]] = arg
52
99
  else:
53
- self.pglog(arg + ": Unknown option", self.LGEREX)
54
- dohelp = False
100
+ self.pglog("-" + opt + ": Unknown option", self.LGEREX)
101
+ opt = None
55
102
  else:
56
- self.pglog(arg + ": Value provided without option", self.LGEREX)
57
- if dohelp:
58
- print("Usage: pgpassword [-l OpenBaoURL] [-k TokenName] [-d DBNAME] \\")
59
- print(" [-c SCHEMA] [-u USName] [-h DBHOST] [-p DBPORT]")
60
- print(" -l OpenBao URL to retrieve passwords")
61
- print(" -k OpenBao Token Name to retrieve passwords")
62
- print(" -d PostgreSQL Database Name")
63
- print(" -c PostgreSQL Schema Name")
64
- print(" -u PostgreSQL Login User Name")
65
- print(" -h PostgreSQL Server Host Name")
66
- print(" -p PostgreSQL Port Number")
67
- sys.exit(0)
103
+ self.pglog(arg + ": value provided without option", self.LGEREX)
104
+ if opt:
105
+ self.pglog("-" + opt + ": missing option value", self.LGEREX)
68
106
 
69
107
  # get the pgpassword
70
108
  def start_actions(self):
109
+ """
110
+ Look up the password and store it in ``self.password``.
111
+
112
+ Applies any CLI-supplied DB overrides via ``default_scinfo``, then
113
+ tries OpenBao first (``get_baopassword``) and falls back to the
114
+ ``.pgpass`` file (``get_pgpassword``) if OpenBao returns nothing.
115
+ """
71
116
  if self.dbopt:
72
117
  self.default_scinfo(self.DBINFO['dbname'], self.DBINFO['scname'], self.DBINFO['dbhost'],
73
118
  self.DBINFO['lnname'], None, self.DBINFO['dbport'])
74
119
  self.password = self.get_baopassword()
75
- if not self.password: self.password = self.get_pg_pass()
120
+ if not self.password: self.password = self.get_pgpassword()
76
121
 
77
- # main function to excecute this script
122
+ # main function to execute this script
78
123
  def main():
124
+ """Entry point for the ``pgpassword`` console script: print the retrieved password to stdout."""
79
125
  object = PgPassword()
80
126
  object.read_parameters()
81
127
  object.start_actions()
@@ -0,0 +1,57 @@
1
+
2
+ Retrieve a PostgreSQL login password for use by RDA python applications.
3
+ The password is looked up first from OpenBao (using the URL and token
4
+ configured in PgDBI) and, if not found there, from the user's .pgpass
5
+ file. The retrieved password is printed to stdout so that shell
6
+ wrappers and other RDA utilities can capture it.
7
+
8
+ Usage: pgpassword [-?] [-l OpenBaoURL] [-k TokenName] [-d DBNAME] \
9
+ [-c SCHEMA] [-u USName] [-h DBHOST] [-p DBPORT]
10
+
11
+ With no options, pgpassword retrieves the password using all defaults
12
+ inherited from PgDBI/PgLOG (see per-option defaults below).
13
+
14
+ - Option -?, show this usage information and exit (also -help
15
+ and --help);
16
+
17
+ - Option -l, OpenBao URL used to retrieve passwords. Overrides
18
+ the BAOURL value from the PgDBI configuration.
19
+ Default: https://bao.k8s.ucar.edu/
20
+
21
+ - Option -k, OpenBao token name used to authenticate the password
22
+ lookup. Overrides the BAOTOKEN value from the PgDBI
23
+ configuration.
24
+
25
+ - Option -d, PostgreSQL database name.
26
+ Default: rdadb
27
+
28
+ - Option -c, PostgreSQL schema name.
29
+ Default: dssdb
30
+
31
+ - Option -u, PostgreSQL login user name.
32
+ Default: dssdb (same as the -c default)
33
+
34
+ - Option -h, PostgreSQL server host name.
35
+ Default: value of the DSSDBHOST environment variable, or
36
+ rda-db.ucar.edu if DSSDBHOST is not set.
37
+
38
+ - Option -p, PostgreSQL server port number.
39
+ Default: 5432
40
+
41
+ If any of -d, -c, -u, -h, or -p is supplied, the values are applied
42
+ via default_scinfo() before the password lookup is performed.
43
+
44
+ Examples:
45
+
46
+ 1. Retrieve the password for the default database/user configured
47
+ in PgDBI:
48
+
49
+ pgpassword
50
+
51
+ 2. Retrieve the password for a specific database, schema and user:
52
+
53
+ pgpassword -d rdadb -c dssdb -u metauser
54
+
55
+ 3. Retrieve the password from a custom OpenBao endpoint:
56
+
57
+ pgpassword -l https://bao.example.org -k my-token -d rdadb
@@ -0,0 +1,305 @@
1
+ Metadata-Version: 2.4
2
+ Name: rda_python_common
3
+ Version: 2.1.9
4
+ Summary: RDA Python common library codes shared by other RDA python packages
5
+ Author-email: Zaihua Ji <zji@ucar.edu>
6
+ Project-URL: Homepage, https://github.com/NCAR/rda-python-common
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: hvac
15
+ Requires-Dist: psycopg2==2.9.10
16
+ Requires-Dist: rda-python-globus
17
+ Requires-Dist: unidecode
18
+ Requires-Dist: hvac
19
+ Dynamic: license-file
20
+
21
+ # rda-python-common
22
+
23
+ Python common library codes to be shared by other RDA python utility programs.
24
+
25
+ ## Installing and using in another RDA python repo
26
+
27
+ `rda-python-common` is the foundation that every other `rda-python-*` repo
28
+ builds on. To consume it from a new or existing repo, follow these steps.
29
+
30
+ ### 1. Install the package
31
+
32
+ For local development, clone this repo alongside your project and install it
33
+ in editable mode so that changes are picked up without re-installing:
34
+
35
+ ```bash
36
+ git clone https://github.com/NCAR/rda-python-common.git
37
+ cd rda-python-common
38
+ pip install -e .
39
+ ```
40
+
41
+ For a regular (non-editable) install from a checkout:
42
+
43
+ ```bash
44
+ pip install /path/to/rda-python-common
45
+ ```
46
+
47
+ For a production install on a system that uses the published distribution:
48
+
49
+ ```bash
50
+ pip install rda_python_common
51
+ ```
52
+
53
+ The package brings in its own transitive dependencies (`psycopg2-binary`,
54
+ `rda-python-globus`, `unidecode`, `hvac`).
55
+
56
+ ### 2. Declare it as a dependency in your project
57
+
58
+ Add `rda_python_common` to the `dependencies` list of your project's
59
+ `pyproject.toml` so that downstream installs pull it in automatically:
60
+
61
+ ```toml
62
+ [project]
63
+ name = "rda_python_yourtool"
64
+ version = "0.1.0"
65
+ dependencies = [
66
+ "rda_python_common",
67
+ # ... other deps
68
+ ]
69
+ ```
70
+
71
+ This is the same pattern used by `rda-python-dsarch`, `rda-python-dsupdt`,
72
+ `rda-python-dsrqst`, `rda-python-dscheck`, `rda-python-metrics`, and
73
+ `rda-python-miscs`.
74
+
75
+ ### 3. Import the modules you need
76
+
77
+ Two import styles are supported (see [Usage examples](#usage-examples) below):
78
+
79
+ ```python
80
+ # Preferred for new code -- import the class from the lower-case module
81
+ from rda_python_common.pg_log import PgLOG
82
+ from rda_python_common.pg_dbi import PgDBI
83
+
84
+ # Legacy module-style imports remain supported for back-compatibility
85
+ from rda_python_common import PgLOG, PgDBI
86
+ PgLOG.pglog("hello", PgLOG.LOGWRN)
87
+ ```
88
+
89
+ ### 4. Verify the install
90
+
91
+ ```bash
92
+ python -c "import rda_python_common; print(rda_python_common.__version__)"
93
+ ```
94
+
95
+ You should see the installed version (currently `2.1.9`). If the import
96
+ fails, double-check that the active Python environment is the one where you
97
+ ran `pip install`.
98
+
99
+ ## Modules
100
+
101
+ All shared functionality lives under `src/rda_python_common/` and is organised as
102
+ a single-inheritance class hierarchy. Each module defines exactly one class;
103
+ later classes extend earlier ones, so an application that instantiates the
104
+ top-of-chain class (typically `PgOPT` or `PgCMD`) gets every helper through one
105
+ object.
106
+
107
+ Inheritance tree (top-down; multi-inheritance shown as two arrows
108
+ converging on the same child):
109
+
110
+ ```
111
+ PgLOG
112
+ ┌────┴────┐
113
+ ▼ ▼
114
+ PgUtil PgDBI
115
+ │ │ │ │ │
116
+ │ └────┐ ┌─┘ │ └─► PgPassword
117
+ │ ▼ ▼ │
118
+ │ PgSplit │ (multi-inherits
119
+ │ │ PgUtil + PgDBI)
120
+ │ ▼
121
+ │ PgSIG
122
+ │ │
123
+ │ ┌──────────┘
124
+ ▼ ▼
125
+ PgFile (multi-inherits
126
+ │ PgUtil + PgSIG)
127
+ ├─► PgOPT
128
+
129
+ └─► PgLock
130
+
131
+ └─► PgCMD
132
+ ```
133
+
134
+ The tree is single inheritance everywhere except at two join points:
135
+
136
+ - **`PgFile(PgUtil, PgSIG)`** — combines date/record utilities (`PgUtil`
137
+ via `PgLOG`) with daemon/signal/DB control (`PgSIG` → `PgDBI` → `PgLOG`),
138
+ so its descendants `PgOPT`, `PgLock`, and `PgCMD` inherit logging, DB,
139
+ util, signal, and file facilities through one MRO.
140
+ - **`PgSplit(PgUtil, PgDBI)`** — combines record-manipulation helpers
141
+ (`PgUtil`) with the `pgadd`/`pgget`/`pgmget`/`pgupdt`/`pgdel` DB
142
+ operations (`PgDBI`) it needs to keep the shared `wfile` table and the
143
+ per-dataset `wfile_<dsid>` partitions in sync.
144
+
145
+ - **`pg_log.py`** — `PgLOG`. Root of the hierarchy. Provides the central
146
+ logging facility (bit-mask `logact` flags such as `MSGLOG`, `WARNLG`,
147
+ `ERRLOG`, `EXITLG`), e-mail dispatch, system-command execution, process
148
+ metadata lookup, and the global `PGLOG` settings dictionary used by every
149
+ other module.
150
+
151
+ - **`pg_util.py`** — `PgUtil(PgLOG)`. Miscellaneous date/time, dataset-ID,
152
+ and column-oriented record-manipulation helpers. Holds the `DATEFMTS`
153
+ regex table, `MONTHS`/`MNS`/`WDAYS`/`WDS` lookup lists, and the `MDAYS`
154
+ days-per-month array used for date arithmetic, formatting, parsing, and
155
+ record sort/search/classification across all RDA tools.
156
+
157
+ - **`pg_file.py`** — `PgFile(PgUtil, PgSIG)`. Unified file-operation layer
158
+ spanning local file systems, remote hosts (rsync/ssh/scp), AWS S3 / object
159
+ store, and Globus endpoints. Used by `rdacp`, `dsarch`, `dsupdt`, and
160
+ related tools whenever data is moved, listed, or stat-ed.
161
+
162
+ - **`pg_lock.py`** — `PgLock(PgFile)`. RDADB record-locking primitives for
163
+ the `dscheck`, `dsrqst`, `dlupdt`, `dcupdt`, `ptrqst`, and `dataset`
164
+ tables. Acquires, refreshes, and releases per-record locks so that
165
+ long-running batch jobs coordinate cleanly.
166
+
167
+ - **`pg_dbi.py`** — `PgDBI(PgLOG)`. PostgreSQL database interface built on
168
+ `psycopg2`. Wraps connection management, batch `INSERT`/`SELECT`/
169
+ `UPDATE`/`DELETE`, transaction control, and credential lookup from
170
+ `.pgpass` or OpenBao. All RDA tools talk to the `rdadb` database through
171
+ this class.
172
+
173
+ - **`pg_sig.py`** — `PgSIG(PgDBI)`. Daemon process control, POSIX signal
174
+ handling, child/background-process management, and PBS/Torque batch-job
175
+ status queries. Provides the `PGSIG` runtime dictionary plus `VUSERS`,
176
+ `CPIDS`, `CBIDS`, and `SDUMP` tables that drive RDA daemon programs.
177
+
178
+ - **`pg_cmd.py`** — `PgCMD(PgLock)`. Manages `dscheck` batch and delayed-
179
+ mode command tracking. Records, updates, and reaps the per-command rows
180
+ that let RDA utilities resume or be monitored across PBS batch jobs.
181
+
182
+ - **`pg_split.py`** — `PgSplit(PgUtil, PgDBI)`. Synchronises `wfile` records
183
+ between the shared `wfile` table and the per-dataset `wfile_<dsid>`
184
+ partition tables. Provides compare/add/update/delete helpers used when
185
+ archiving or reconciling dataset file inventories.
186
+
187
+ - **`pg_opt.py`** — `PgOPT(PgFile)`. Command-line option parsing and
188
+ application configuration framework for RDA tools (`dsarch`, `dsupdt`,
189
+ `dsrqst`, ...). Holds the master `OPTS` definition table, parsed
190
+ `params`, command-line vs. input-file option tracking (`CMDOPTS`/
191
+ `INOPTS`), output formatting, dataset/help/media/storage/backup type
192
+ maps, and the global `PGOPT` settings.
193
+
194
+ - **`pgpassword.py`** — `PgPassword(PgDBI)`. Standalone CLI entry point
195
+ (`pgpassword`) that resolves a PostgreSQL login password from OpenBao
196
+ (`get_baopassword`) or `~/.pgpass` (`get_pgpassword()`) given database/schema/
197
+ host/port/user selectors via `-d`, `-c`, `-h`, `-p`, `-u`, `-l`, `-k`.
198
+ Prints the resolved password to stdout so shell scripts can capture it.
199
+
200
+ ## Usage examples
201
+
202
+ Each class lives in its own submodule. Import the class you need, then
203
+ either instantiate it directly or subclass it to add application-specific
204
+ state and methods.
205
+
206
+ ### 1. Direct instantiation — use the helpers as-is
207
+
208
+ ```python
209
+ # Logging only
210
+ from rda_python_common.pg_log import PgLOG
211
+
212
+ log = PgLOG()
213
+ log.pglog("dsarch started", log.LOGWRN)
214
+
215
+ # Database access (PgDBI inherits PgLOG, so you get logging too)
216
+ from rda_python_common.pg_dbi import PgDBI
217
+
218
+ db = PgDBI()
219
+ rec = db.pgget('dataset', 'dsid, title', "dsid = 'd633000'")
220
+ print(rec)
221
+ ```
222
+
223
+ ### 2. Subclassing a single common class
224
+
225
+ ```python
226
+ # A small utility that needs date/record helpers plus logging.
227
+ from rda_python_common.pg_util import PgUtil
228
+
229
+ class DateReport(PgUtil):
230
+ def __init__(self):
231
+ super().__init__() # initialise PgUtil (and PgLOG)
232
+ self.today = self.curtime() # method inherited from PgUtil
233
+
234
+ def run(self):
235
+ self.pglog(f"report date: {self.today}", self.LOGWRN)
236
+
237
+ DateReport().run()
238
+ ```
239
+
240
+ ### 3. Subclassing one of the multi-inheriting joins
241
+
242
+ ```python
243
+ # A worker that needs file I/O (PgFile) and dscheck command tracking (PgCMD).
244
+ # PgCMD already extends PgFile via PgLock, so a single base is enough.
245
+ from rda_python_common.pg_cmd import PgCMD
246
+
247
+ class Worker(PgCMD):
248
+ def __init__(self):
249
+ super().__init__()
250
+ self.jobs = []
251
+
252
+ def archive_one(self, src, dst):
253
+ # PgFile method, available through the inheritance chain
254
+ self.local_copy_local(src, dst)
255
+ # PgDBI method, available through PgCMD -> PgLock -> PgFile -> PgSIG -> PgDBI
256
+ self.pgupdt('wfile', {'status': 'A'}, f"wfile = '{dst}'")
257
+
258
+ Worker().archive_one('/in/file', '/out/file')
259
+ ```
260
+
261
+ ### 4. Combining multiple common classes (application action class)
262
+
263
+ This mirrors how RDA tools such as `dsarch` are structured. The leaf class
264
+ multi-inherits several common classes so a single object exposes options,
265
+ command tracking, and wfile splitting.
266
+
267
+ ```python
268
+ # Excerpt of the pattern used by rda_python_dsarch/dsarch.py
269
+ from rda_python_common.pg_opt import PgOPT
270
+ from rda_python_common.pg_cmd import PgCMD
271
+ from rda_python_common.pg_split import PgSplit
272
+
273
+ class PgArch(PgOPT, PgCMD, PgSplit):
274
+ """Shared state + helpers for a CLI archiving tool."""
275
+ def __init__(self):
276
+ super().__init__()
277
+ self.RTPATH = {} # runtime path cache
278
+ self.OPTS = {} # option table (populated by subclass)
279
+
280
+ class DsArch(PgArch):
281
+ def __init__(self):
282
+ super().__init__()
283
+ self.ALLCNT = self.ADDCNT = self.MODCNT = 0
284
+
285
+ def main(self):
286
+ self.read_parameters() # from PgOPT
287
+ self.start_actions() # dispatch
288
+
289
+ if __name__ == "__main__":
290
+ DsArch().main()
291
+ ```
292
+
293
+ ### 5. Reading a PostgreSQL password from OpenBao or ~/.pgpass
294
+
295
+ ```python
296
+ from rda_python_common.pgpassword import PgPassword
297
+
298
+ pw = PgPassword()
299
+ pw.default_scinfo('rdadb', 'dssdb', 'rda-pgdb', 'gdexweb', None, 5432)
300
+ password = pw.get_baopassword() or pw.get_pgpassword()
301
+ ```
302
+
303
+ In every case `super().__init__()` cooperates correctly across the
304
+ multi-inheriting joins (`PgFile` and `PgSplit`), so subclasses only need
305
+ to call it once.
@@ -0,0 +1,28 @@
1
+ rda_python_common/PgCMD.py,sha256=EYjG2Z4zEnvsXE1z-jt5UaNoEKxnOYYiMMzvW6HrKA4,20597
2
+ rda_python_common/PgDBI.py,sha256=sOKQmzF_qmLR_vnj-NnFEYbLI05t2bVbhqlqy46iluA,76424
3
+ rda_python_common/PgFile.py,sha256=7hyDFm40_NjQ_tg5WviMw_waYPdyXCi0DRCf0Av8eNE,99370
4
+ rda_python_common/PgLOG.py,sha256=43YVpMjWEOLpaMUb4YTEYlDTvp7FTmDQ8V3TAKQrC4M,55228
5
+ rda_python_common/PgLock.py,sha256=12i84nsGBuifSyPnm8IR63LvHvRuVU573D5QKFlHdOI,22623
6
+ rda_python_common/PgOPT.py,sha256=Kn4JYezZhZwAn2usiIYHoHiymGHRgsN299dxyXbKwkI,56244
7
+ rda_python_common/PgSIG.py,sha256=eTJJ3XxutsQ3JSg6uYRpH26ZmrVUup3ShqDFLWXLruA,35803
8
+ rda_python_common/PgSplit.py,sha256=SSg5_Qu5PqP44EkqebO-V_cErNcdE2QtORgFHQ7RqlQ,8822
9
+ rda_python_common/PgUtil.py,sha256=OqESKCd72b9g8m8jwjPJhXDtPYlW6G8oSOhwChvz2Cg,48600
10
+ rda_python_common/__init__.py,sha256=_zWhlp02qZlgMlsox5HGR_mmXRjyQSHIo__v_hTW2rY,994
11
+ rda_python_common/pg_cmd.py,sha256=hQZaW80eFqEUoF0vZHtGuvBEWXbXdY0Nvj6rFIQGboo,32856
12
+ rda_python_common/pg_dbi.py,sha256=q6LT4a4lwo-sM2LEqgrEVkAlh886xeKiz4feqRKw-7c,116238
13
+ rda_python_common/pg_file.py,sha256=RzQTxnht4bFOVty4qoYPREawMCUsnFIbWm2pfEez1fE,161993
14
+ rda_python_common/pg_lock.py,sha256=31EaVDjCkcx3-n8-KnzG18R8Pz7Z6KyFsEqcml6Iq5c,32702
15
+ rda_python_common/pg_log.py,sha256=5kasi1aucWWhlGRlvOG9MT4eHmNsPqEb09rFn_kHPjg,80669
16
+ rda_python_common/pg_opt.py,sha256=sXrlzFWpR2XMak6NlA0MPvErBuQnY6gHM5OsHtoeIPQ,82496
17
+ rda_python_common/pg_password.py,sha256=X-eIDwdqBhtrhrbDTNWle-0JtWsyIVZdDOZaBu7cFHM,2343
18
+ rda_python_common/pg_sig.py,sha256=wLKBmFNWAmxWJFC2L3SLsD4n464kzzHbIFFNBlCW6kI,52759
19
+ rda_python_common/pg_split.py,sha256=aAWKUZPmZ-LQ_fJ3DSKzPKxJw0fMDJ2fgP8ZT629M30,16375
20
+ rda_python_common/pg_util.py,sha256=bjp2civRIhqaBSR8oOtyRzYIZBdwB90SzmJLjRIA7fc,87280
21
+ rda_python_common/pgpassword.py,sha256=aTQaO59QokWrv2EKom4TvKhNW2Mr6aFMMBVG4DCbcKI,5023
22
+ rda_python_common/pgpassword.usg,sha256=Wrle6A8sdlMGx3ZEAmE6TNDPOlG84Whe0T3rCNFzPfY,2013
23
+ rda_python_common-2.1.9.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
24
+ rda_python_common-2.1.9.dist-info/METADATA,sha256=RCXhbSP_OnuQyIUUa5h67nFYjzElGU2uDBMXoH5wFcY,11090
25
+ rda_python_common-2.1.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
26
+ rda_python_common-2.1.9.dist-info/entry_points.txt,sha256=pZgVNWspcK-F1TbPav7C3C9NdeHDZMm_25fW9weix00,65
27
+ rda_python_common-2.1.9.dist-info/top_level.txt,sha256=KVQmx7D3DD-jsiheqL8HdTrRE14hpRnZY5_ioMArA5k,18
28
+ rda_python_common-2.1.9.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rda_python_common
3
- Version: 2.1.7
4
- Summary: RDA Python common library codes shared by other RDA python packages
5
- Author-email: Zaihua Ji <zji@ucar.edu>
6
- Project-URL: Homepage, https://github.com/NCAR/rda-python-common
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: hvac
15
- Requires-Dist: psycopg2==2.9.10
16
- Requires-Dist: rda-python-globus
17
- Requires-Dist: unidecode
18
- Dynamic: license-file
19
-
20
- # rda-python-common
21
- Python common library codes to be shared by other RDA python utility programs.
@@ -1,27 +0,0 @@
1
- rda_python_common/PgCMD.py,sha256=EYjG2Z4zEnvsXE1z-jt5UaNoEKxnOYYiMMzvW6HrKA4,20597
2
- rda_python_common/PgDBI.py,sha256=sOKQmzF_qmLR_vnj-NnFEYbLI05t2bVbhqlqy46iluA,76424
3
- rda_python_common/PgFile.py,sha256=7hyDFm40_NjQ_tg5WviMw_waYPdyXCi0DRCf0Av8eNE,99370
4
- rda_python_common/PgLOG.py,sha256=43YVpMjWEOLpaMUb4YTEYlDTvp7FTmDQ8V3TAKQrC4M,55228
5
- rda_python_common/PgLock.py,sha256=12i84nsGBuifSyPnm8IR63LvHvRuVU573D5QKFlHdOI,22623
6
- rda_python_common/PgOPT.py,sha256=Kn4JYezZhZwAn2usiIYHoHiymGHRgsN299dxyXbKwkI,56244
7
- rda_python_common/PgSIG.py,sha256=eTJJ3XxutsQ3JSg6uYRpH26ZmrVUup3ShqDFLWXLruA,35803
8
- rda_python_common/PgSplit.py,sha256=SSg5_Qu5PqP44EkqebO-V_cErNcdE2QtORgFHQ7RqlQ,8822
9
- rda_python_common/PgUtil.py,sha256=OqESKCd72b9g8m8jwjPJhXDtPYlW6G8oSOhwChvz2Cg,48600
10
- rda_python_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- rda_python_common/pg_cmd.py,sha256=hQZaW80eFqEUoF0vZHtGuvBEWXbXdY0Nvj6rFIQGboo,32856
12
- rda_python_common/pg_dbi.py,sha256=R-ZzHUItAxma_rCTrelDakFQDl8yEtOTKo4IRfzKl_Q,116269
13
- rda_python_common/pg_file.py,sha256=doIXpl0I8gO1yiNofipjwZmHF6TYQkWIEj9XrMRz4XQ,162013
14
- rda_python_common/pg_lock.py,sha256=Il-pY81JtEDMWtwqW6lEM-EtKbhNI6rug8VMCdkGgNc,32703
15
- rda_python_common/pg_log.py,sha256=Zte9joy46hn2RsJPNpisHLjxZI-mDdcXY4aSiMNlIoY,80661
16
- rda_python_common/pg_opt.py,sha256=N45DIY2O3S7ogABOHUecfiKwQY1_O4X7WYYZGLdnhOI,82506
17
- rda_python_common/pg_password.py,sha256=X-eIDwdqBhtrhrbDTNWle-0JtWsyIVZdDOZaBu7cFHM,2343
18
- rda_python_common/pg_sig.py,sha256=Dz7QKOkwYchbhVZgQxQqFrUmGaxer2bm8D2K99ig5L0,52760
19
- rda_python_common/pg_split.py,sha256=yOeUSRzgQlwNGzv76ZLCZtsjzDQw0NeYXz0IV0RZgXQ,16343
20
- rda_python_common/pg_util.py,sha256=1f25D4xyPMerW55q42CQOD0WYAoFYL2A8Opl4rHA8N4,87393
21
- rda_python_common/pgpassword.py,sha256=WXEq88XgkQSmJ2j2MX0HSVEUFZOFu-gokRJYvEQj_u4,2900
22
- rda_python_common-2.1.7.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
23
- rda_python_common-2.1.7.dist-info/METADATA,sha256=HBIupC4bHGhJ3H9tanrlZxjiw_SuDKChlvB7E06YfGs,761
24
- rda_python_common-2.1.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
25
- rda_python_common-2.1.7.dist-info/entry_points.txt,sha256=pZgVNWspcK-F1TbPav7C3C9NdeHDZMm_25fW9weix00,65
26
- rda_python_common-2.1.7.dist-info/top_level.txt,sha256=KVQmx7D3DD-jsiheqL8HdTrRE14hpRnZY5_ioMArA5k,18
27
- rda_python_common-2.1.7.dist-info/RECORD,,