rda-python-common 3.0.14__tar.gz → 3.0.15__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.14/src/rda_python_common.egg-info → rda_python_common-3.0.15}/PKG-INFO +2 -2
  2. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/README.md +1 -1
  3. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/pyproject.toml +1 -1
  4. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_dbi.py +111 -23
  6. {rda_python_common-3.0.14 → rda_python_common-3.0.15/src/rda_python_common.egg-info}/PKG-INFO +2 -2
  7. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/LICENSE +0 -0
  8. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/setup.cfg +0 -0
  9. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgCMD.py +0 -0
  10. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgDBI.py +0 -0
  11. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgFile.py +0 -0
  12. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgLOG.py +0 -0
  13. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgLock.py +0 -0
  14. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgOPT.py +0 -0
  15. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgSIG.py +0 -0
  16. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgSplit.py +0 -0
  17. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/PgUtil.py +0 -0
  18. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_cmd.py +0 -0
  19. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_file.py +0 -0
  20. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_lock.py +0 -0
  21. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_log.py +0 -0
  22. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_opt.py +0 -0
  23. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_password.py +0 -0
  24. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_sig.py +0 -0
  25. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.14 → rda_python_common-3.0.15}/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.14
3
+ Version: 3.0.15
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.14`). If the import
192
+ You should see the installed version (currently `3.0.15`). 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.14`). If the import
168
+ You should see the installed version (currently `3.0.15`). 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.14"
7
+ version = "3.0.15"
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.14"
25
+ __version__ = "3.0.15"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -76,7 +76,7 @@ class PgDBI(PgLOG):
76
76
  using psycopg (v3) when available, falling back to psycopg2. Supports single
77
77
  and batch INSERT, SELECT, UPDATE, and DELETE operations, transaction
78
78
  management, schema introspection, user lookups, usage tracking, and
79
- credential retrieval from .pgpass or OpenBao.
79
+ credential retrieval from the environment, .pgpass or OpenBao.
80
80
 
81
81
  Inherits all logging and utility helpers from PgLOG.
82
82
 
@@ -96,7 +96,10 @@ class PgDBI(PgLOG):
96
96
  set from psycopg/psycopg2 depending on which driver is in use.
97
97
  DBPORTS (dict): Mapping of database names to non-default TCP port numbers.
98
98
  DBPASS (dict): Credentials loaded from .pgpass, keyed by (host, port, db, user).
99
- DBBAOS (dict): Credentials loaded from OpenBao, keyed by database name.
99
+ DBBAOS (dict): Credentials loaded from OpenBao, keyed by db server node.
100
+ DBNODES (dict): Mapping of database names to their db server nodes, used when the
101
+ node cannot be taken from the host being connected to.
102
+ DBSKEYS (dict): Mapping of login names to secret key names ('<lnname>pass' by default).
100
103
  DBNAMES (dict): Mapping of schema names to their parent database names.
101
104
  DBSOCKS (dict): Mapping of database names to Unix socket paths.
102
105
  VIEWHOMES (dict): Mapping of hostnames to home directories for the view host.
@@ -139,6 +142,19 @@ class PgDBI(PgLOG):
139
142
  self.DBPORTS = {'default': 0}
140
143
  self.DBPASS = {}
141
144
  self.DBBAOS = {}
145
+ # hard coded db server nodes for dbnames, for hosts not named for their nodes; a node
146
+ # names the OpenBao path (kv/gdex/<node>) and the password environment variable
147
+ # (<NODE>_<KEY>) the same secrets are passed in through
148
+ self.DBNODES = {
149
+ 'ivaddb': 'pgdb03',
150
+ 'ispddb': 'pgdb03',
151
+ 'default': 'pgdb01',
152
+ }
153
+ # hard coded secret key names for db login names, defaulting to <lnname>pass
154
+ self.DBSKEYS = {
155
+ 'postgres': 'password',
156
+ 'metadata': 'metapass',
157
+ }
142
158
  # hard coded db names for given schema names
143
159
  self.DBNAMES = {
144
160
  'ivaddb': 'ivaddb',
@@ -2617,19 +2633,91 @@ class PgDBI(PgLOG):
2617
2633
  def get_pgpass_password(self):
2618
2634
  """Return the database password for the current connection settings.
2619
2635
 
2620
- Checks PGDBI['PWNAME'] first, then tries the .pgpass file (get_pgpassword()),
2621
- and finally falls back to OpenBao (get_baopassword()).
2636
+ Checks PGDBI['PWNAME'] first, then the environment (get_envpassword(), as set
2637
+ for containers on CIRRUS), then the .pgpass file (get_pgpassword()), and
2638
+ finally falls back to OpenBao (get_baopassword()). A password found on local
2639
+ or batch hosts is cached in the environment (set_envpassword()) so that the
2640
+ child processes inherit it instead of looking it up again.
2622
2641
 
2623
2642
  Returns:
2624
2643
  str | None: Password string, or None when no credential is found.
2625
2644
  """
2626
2645
  if self.PGDBI['PWNAME']: return self.PGDBI['PWNAME']
2646
+ pwname = self.get_envpassword()
2647
+ if pwname: return pwname
2627
2648
  pwname = self.get_pgpassword()
2628
2649
  if not pwname: pwname = self.get_baopassword()
2629
- if not pwname:
2630
- self.pglog("Unable to find password for {} in .pgpass or OpenBao".format(self.PGDBI['DBNAME']), self.PGDBI['ERRLOG'])
2650
+ if pwname:
2651
+ self.set_envpassword(pwname)
2652
+ else:
2653
+ self.pglog("Unable to find password for {} in environment, .pgpass or OpenBao".format(self.PGDBI['DBNAME']), self.PGDBI['ERRLOG'])
2631
2654
  return pwname
2632
2655
 
2656
+ def get_dbnodes(self):
2657
+ """Return the db server nodes holding the secrets of the current connection.
2658
+
2659
+ The node named by DBSHOST comes first, such as pgdb02 for the view-only host,
2660
+ so that a node with its own passwords is served its own secrets. The DBNODES
2661
+ node of the current DBNAME follows it as a fallback, such as pgdb01 for pgdb02,
2662
+ for a node that shares the passwords of the default one without holding secrets
2663
+ of its own.
2664
+
2665
+ Returns:
2666
+ list: Node names, such as ['pgdb02', 'pgdb01'], the first one preferred.
2667
+ """
2668
+ dbnodes = []
2669
+ ms = re.match(r'^(pgdb\d+)$', self.PGDBI['DBSHOST']) if self.PGDBI['DBSHOST'] else None
2670
+ if ms: dbnodes.append(ms.group(1))
2671
+ dbnode = self.DBNODES.get(self.PGDBI['DBNAME'], self.DBNODES['default'])
2672
+ if dbnode not in dbnodes: dbnodes.append(dbnode)
2673
+
2674
+ return dbnodes
2675
+
2676
+ def get_secret_name(self, dbnode):
2677
+ """Build the name of the secret holding the password of the current connection.
2678
+
2679
+ The key comes from DBSKEYS for the current LNNAME, defaulting to '<lnname>pass'.
2680
+ The pair names the secret in OpenBao (kv/gdex/<node> key <key>) and the
2681
+ environment variable (<NODE>_<KEY>) the same secret is passed in through
2682
+ on CIRRUS.
2683
+
2684
+ Args:
2685
+ dbnode (str): Db server node holding the secret, such as 'pgdb01'.
2686
+
2687
+ Returns:
2688
+ tuple: (node, key) naming the db server node and the secret key.
2689
+ """
2690
+ lnname = self.PGDBI['LNNAME']
2691
+
2692
+ return (dbnode, self.DBSKEYS.get(lnname, lnname + 'pass'))
2693
+
2694
+ def get_envpassword(self):
2695
+ """Look up the password in the environment, as set for containers on CIRRUS.
2696
+
2697
+ The variable name is the uppercased '<node>_<key>' secret name, for example
2698
+ PGDB01_DSSDBPASS for login name dssdb and PGDB03_IVADDBPASS for ivaddb. The
2699
+ fallback nodes of get_dbnodes() are tried in turn.
2700
+
2701
+ Returns:
2702
+ str | None: Password string, or None when no variable is set.
2703
+ """
2704
+ for dbnode in self.get_dbnodes():
2705
+ pwname = os.environ.get("{}_{}".format(*self.get_secret_name(dbnode)).upper())
2706
+ if pwname: return pwname
2707
+
2708
+ return None
2709
+
2710
+ def set_envpassword(self, pwname):
2711
+ """Cache a password in the environment under the name get_envpassword() reads.
2712
+
2713
+ Called for a password found in .pgpass or OpenBao, on local and PBS batch hosts,
2714
+ so that the child processes of the current one inherit it and skip the lookup.
2715
+
2716
+ Args:
2717
+ pwname (str): Password to pass on to the child processes.
2718
+ """
2719
+ os.environ["{}_{}".format(*self.get_secret_name(self.get_dbnodes()[0])).upper()] = pwname
2720
+
2633
2721
  def get_pgpassword(self):
2634
2722
  """Look up the password in the cached .pgpass credentials.
2635
2723
 
@@ -2646,16 +2734,20 @@ class PgDBI(PgLOG):
2646
2734
  return pwname
2647
2735
 
2648
2736
  def get_baopassword(self):
2649
- """Look up the password from OpenBao for the current database and login name.
2737
+ """Look up the password from OpenBao for the current db server node and login name.
2650
2738
 
2651
- Loads OpenBao secrets for PGDBI['DBNAME'] on first call (or when not cached).
2739
+ Loads the OpenBao secrets of a node on first call (or when not cached), trying
2740
+ the fallback nodes of get_dbnodes() in turn.
2652
2741
 
2653
2742
  Returns:
2654
2743
  str | None: Password string, or None when not found in OpenBao.
2655
2744
  """
2656
- dbname = self.PGDBI['DBNAME']
2657
- if dbname not in self.DBBAOS: self.read_openbao()
2658
- return self.DBBAOS[dbname].get(self.PGDBI['LNNAME'])
2745
+ for dbnode in self.get_dbnodes():
2746
+ if dbnode not in self.DBBAOS: self.read_openbao(dbnode)
2747
+ pwname = self.DBBAOS[dbnode].get(self.PGDBI['LNNAME'])
2748
+ if pwname: return pwname
2749
+
2750
+ return None
2659
2751
 
2660
2752
  def read_pgpass(self):
2661
2753
  """Read the .pgpass file and populate DBPASS with credentials.
@@ -2675,21 +2767,17 @@ class PgDBI(PgLOG):
2675
2767
  except Exception:
2676
2768
  pass
2677
2769
 
2678
- def read_openbao(self):
2679
- """Read OpenBao secrets and populate DBBAOS with credentials for DBNAME.
2770
+ def read_openbao(self, dbnode):
2771
+ """Read OpenBao secrets and populate DBBAOS with the credentials of a db server node.
2680
2772
 
2681
2773
  Uses the hvac client to fetch key-value secrets from the configured BAOURL.
2682
2774
  Parses keys matching 'pass' patterns to extract database usernames and passwords.
2775
+
2776
+ Args:
2777
+ dbnode (str): Db server node naming the secret path, such as 'pgdb01'.
2683
2778
  """
2684
- dbname = self.PGDBI['DBNAME']
2685
- self.DBBAOS[dbname] = {}
2686
- url = 'https://bao.k8s.ucar.edu/'
2687
- baopath = {
2688
- 'ivaddb': 'gdex/pgdb03',
2689
- 'ispddb': 'gdex/pgdb03',
2690
- 'default': 'gdex/pgdb01'
2691
- }
2692
- dbpath = baopath[dbname] if dbname in baopath else baopath['default']
2779
+ self.DBBAOS[dbnode] = {}
2780
+ dbpath = 'gdex/' + dbnode
2693
2781
  client = hvac.Client(url=self.PGDBI.get('BAOURL'))
2694
2782
  client.token = self.PGLOG.get('BAOTOKEN')
2695
2783
  try:
@@ -2711,4 +2799,4 @@ class PgDBI(PgLOG):
2711
2799
  baoname = 'metadata' if pre == 'meta' else pre
2712
2800
  elif suf == 'word':
2713
2801
  baoname = 'postgres'
2714
- if baoname: self.DBBAOS[dbname][baoname] = baos[key]
2802
+ if baoname: self.DBBAOS[dbnode][baoname] = baos[key]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.14
3
+ Version: 3.0.15
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.14`). If the import
192
+ You should see the installed version (currently `3.0.15`). 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