rda-python-common 3.0.6__tar.gz → 3.0.9__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.6/src/rda_python_common.egg-info → rda_python_common-3.0.9}/PKG-INFO +1 -1
  2. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/pyproject.toml +1 -1
  3. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_dbi.py +103 -23
  4. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_log.py +3 -9
  5. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_opt.py +3 -3
  6. {rda_python_common-3.0.6 → rda_python_common-3.0.9/src/rda_python_common.egg-info}/PKG-INFO +1 -1
  7. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/LICENSE +0 -0
  8. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/README.md +0 -0
  9. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/setup.cfg +0 -0
  10. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgCMD.py +0 -0
  11. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgDBI.py +0 -0
  12. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgFile.py +0 -0
  13. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgLOG.py +0 -0
  14. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgLock.py +0 -0
  15. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgOPT.py +0 -0
  16. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgSIG.py +0 -0
  17. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgSplit.py +0 -0
  18. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/PgUtil.py +0 -0
  19. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/__init__.py +0 -0
  20. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_cmd.py +0 -0
  21. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_file.py +0 -0
  22. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_lock.py +0 -0
  23. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_password.py +0 -0
  24. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_sig.py +0 -0
  25. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.6 → rda_python_common-3.0.9}/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.6
3
+ Version: 3.0.9
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_common"
7
- version = "3.0.6"
7
+ version = "3.0.9"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -85,6 +85,7 @@ class PgDBI(PgLOG):
85
85
  curtran (int): Transaction counter: 0 = idle, >0 = inside a transaction.
86
86
  NMISSES (list): Cached list of scientist IDs (userno) not found in the DB.
87
87
  LMISSES (list): Cached list of login names not found in the DB.
88
+ USRWARN (int): Set to 1 once the 'run filluser' reminder has been logged.
88
89
  TABLES (dict): Cache of table-field default info keyed by table name.
89
90
  SEQUENCES (dict): Cache of sequence field names keyed by table name.
90
91
  SPECIALIST (dict): Cache of specialist records keyed by dataset ID.
@@ -125,6 +126,7 @@ class PgDBI(PgLOG):
125
126
  self.curtran = 0 # 0 - no transaction, 1 - in transaction
126
127
  self.NMISSES = [] # array of mising userno
127
128
  self.LMISSES = [] # array of mising logname
129
+ self.USRWARN = 0 # 1 - reminder for incomplete user records given already
128
130
  self.TABLES = {} # record table field information
129
131
  self.SEQUENCES = {} # record table sequence fielnames
130
132
  self.SPECIALIST = {} # hash array refrences to specialist info of dsids
@@ -1699,6 +1701,82 @@ class PgDBI(PgLOG):
1699
1701
  ret = self.pgget('information_schema.tables', None, condition, logact)
1700
1702
  return (self.SUCCESS if ret else self.FAILURE)
1701
1703
 
1704
+ def dssgrp_user_info(self, userno, logname = None):
1705
+ """Build a partial dssdb.user record from dssdb.dssgrp.
1706
+
1707
+ Fallback for when the UCAR People API is unreachable, so a usable user record
1708
+ can still be added for a DECS group member. Fields absent from dssgrp are set
1709
+ to the values implied by DECS membership; the record is marked stat_flag 'M'
1710
+ so that 'filluser -i' completes it once the People API is available again.
1711
+
1712
+ Args:
1713
+ userno (int): Scientist number; pass 0 to look up by logname instead.
1714
+ logname (str | None): UCAR login name; used when userno is 0.
1715
+
1716
+ Returns:
1717
+ dict | None: Partial mapping of column_name → value suitable for
1718
+ pgadd('dssdb.user'), or None when not in dssgrp.
1719
+ """
1720
+ cond = "userno = {}".format(userno) if userno else "logname = '{}'".format(logname)
1721
+ pgrec = self.pgget("dssgrp", "logname, userno, lstname, fstname, midinit, phoneno", cond, self.PGDBI['ERRLOG'])
1722
+ if not pgrec: return None
1723
+ if not pgrec['userno']: del pgrec['userno'] # leave NULL; 0 is never looked up
1724
+ email = pgrec['logname'] + '@ucar.edu'
1725
+ pgrec['org_type'] = 'DSS'
1726
+ pgrec['org_name'] = 'NCAR'
1727
+ pgrec['country'] = 'UNITED.STATES'
1728
+ pgrec['email'] = pgrec['ucaremail'] = email
1729
+ pgrec['stat_flag'] = 'M'
1730
+ return pgrec
1731
+
1732
+ def add_missed_user(self, userno, logname = None):
1733
+ """Add a dssdb.user record for a user missing from the table.
1734
+
1735
+ Tries the UCAR People API first, then dssgrp, and finally falls back to a bare
1736
+ stub. A reminder to run filluser is logged once per process whenever the added
1737
+ record is incomplete (stat_flag 'M').
1738
+
1739
+ Args:
1740
+ userno (int): Scientist number; pass 0 to add by logname instead.
1741
+ logname (str | None): UCAR login name; used when userno is 0.
1742
+
1743
+ Returns:
1744
+ int: The new user.uid, or 0 if the insert fails.
1745
+ """
1746
+ pgrec = self.ucar_user_info(userno, logname)
1747
+ if not pgrec: pgrec = self.dssgrp_user_info(userno, logname)
1748
+ if not pgrec:
1749
+ pgrec = {'userno': userno} if userno else {'logname': logname}
1750
+ pgrec['stat_flag'] = 'M'
1751
+ if pgrec['stat_flag'] == 'M': self.incomplete_user_warning()
1752
+ return self.pgadd("dssdb.user", pgrec, (self.PGDBI['EXITLG']|self.AUTOID))
1753
+
1754
+ def incomplete_user_warning(self):
1755
+ """Log a one-time reminder to run filluser for incomplete dssdb.user records."""
1756
+ if self.USRWARN: return
1757
+ self.USRWARN = 1
1758
+ self.pglog("Incomplete record in dssdb.user; run 'filluser -i' later to complete it", self.LGWNEM)
1759
+
1760
+ def reopen_user_record(self, pgrec):
1761
+ """Reactivate a closed dssdb.user record and return its uid.
1762
+
1763
+ Clears until_date and marks the record incomplete (stat_flag 'M') so that
1764
+ 'filluser -i' refreshes it. Records that are not closed are returned as is.
1765
+ Only call this for a present-day lookup; reopening on a historical lookup
1766
+ would wrongly reactivate a user who has since left.
1767
+
1768
+ Args:
1769
+ pgrec (dict): Record holding at least 'uid' and 'until_date'.
1770
+
1771
+ Returns:
1772
+ int: The uid of the record.
1773
+ """
1774
+ if pgrec['until_date']:
1775
+ record = {'until_date': None, 'stat_flag': 'M'}
1776
+ self.pgupdt("dssdb.user", record, "uid = {}".format(pgrec['uid']), self.PGDBI['ERRLOG'])
1777
+ self.pglog("Closed user.uid = {} is reopened".format(pgrec['uid']), self.LGWNEM)
1778
+ return pgrec['uid']
1779
+
1702
1780
  def check_user_uid(self, userno, date = None):
1703
1781
  """Return the user.uid for a scientist ID, adding a record if missing.
1704
1782
 
@@ -1716,6 +1794,7 @@ class PgDBI(PgLOG):
1716
1794
  """
1717
1795
  if not userno: return 0
1718
1796
  if type(userno) is str: userno = int(userno)
1797
+ istoday = (date is None)
1719
1798
  if date is None:
1720
1799
  datecond = "until_date IS NULL"
1721
1800
  date = 'today'
@@ -1727,11 +1806,9 @@ class PgDBI(PgLOG):
1727
1806
  self.pglog("{}: Scientist ID NOT on file for {}".format(userno, date), self.LGWNEM)
1728
1807
  self.NMISSES.append(userno)
1729
1808
  # check again if a user is on file with different date range
1730
- pgrec = self.pgget("dssdb.user", "uid", "userno = {}".format(userno), self.PGDBI['ERRLOG'])
1731
- if pgrec: return pgrec['uid']
1732
- pgrec = self.ucar_user_info(userno)
1733
- if not pgrec: pgrec = {'userno': userno, 'stat_flag': 'M'}
1734
- uid = self.pgadd("dssdb.user", pgrec, (self.PGDBI['EXITLG']|self.AUTOID))
1809
+ pgrec = self.pgget("dssdb.user", "uid, until_date", "userno = {}".format(userno), self.PGDBI['ERRLOG'])
1810
+ if pgrec: return self.reopen_user_record(pgrec) if istoday else pgrec['uid']
1811
+ uid = self.add_missed_user(userno)
1735
1812
  if uid: self.pglog("{}: Scientist ID Added as user.uid = {}".format(userno, uid), self.LGWNEM)
1736
1813
  return uid
1737
1814
 
@@ -1748,6 +1825,7 @@ class PgDBI(PgLOG):
1748
1825
  int: user.uid on success, 0 if logname is falsy or insert fails.
1749
1826
  """
1750
1827
  if not logname: return 0
1828
+ istoday = (not date)
1751
1829
  if not date:
1752
1830
  date = 'today'
1753
1831
  datecond = "until_date IS NULL"
@@ -1759,11 +1837,9 @@ class PgDBI(PgLOG):
1759
1837
  self.pglog("{}: UCAR Login Name NOT on file for {}".format(logname, date), self.LGWNEM)
1760
1838
  self.LMISSES.append(logname)
1761
1839
  # check again if a user is on file with different date range
1762
- pgrec = self.pgget("dssdb.user", "uid", "logname = '{}'".format(logname), self.PGDBI['ERRLOG'])
1763
- if pgrec: return pgrec['uid']
1764
- pgrec = self.ucar_user_info(0, logname)
1765
- if not pgrec: pgrec = {'logname': logname, 'stat_flag': 'M'}
1766
- uid = self.pgadd("dssdb.user", pgrec, (self.PGDBI['EXITLG']|self.AUTOID))
1840
+ pgrec = self.pgget("dssdb.user", "uid, until_date", "logname = '{}'".format(logname), self.PGDBI['ERRLOG'])
1841
+ if pgrec: return self.reopen_user_record(pgrec) if istoday else pgrec['uid']
1842
+ uid = self.add_missed_user(0, logname)
1767
1843
  if uid: self.pglog("{}: UCAR Login Name Added as user.uid = {}".format(logname, uid), self.LGWNEM)
1768
1844
  return uid
1769
1845
 
@@ -1796,6 +1872,9 @@ class PgDBI(PgLOG):
1796
1872
  'email': "ucaremail",
1797
1873
  'phone': "phoneno"
1798
1874
  }
1875
+ # pgperson/pgusername are installed by rda_python_metrics; skip quietly when the
1876
+ # commands are not on this host so that callers can fall back to dssgrp
1877
+ if not self.valid_command("pgperson"): return None
1799
1878
  buf = self.pgsystem("pgperson " + ("-uid {}".format(userno) if userno else "-username {}".format(logname)), self.LOGWRN, 20)
1800
1879
  if not buf: return None
1801
1880
  pgrec = {}
@@ -1807,23 +1886,24 @@ class PgDBI(PgLOG):
1807
1886
  if key == 'upid' and 'upid' in pgrec: break # get one record only
1808
1887
  pgrec[matches[key]] = val
1809
1888
  if not pgrec: return None
1889
+ # the People API omits keys it has no value for, so read them defensively
1810
1890
  if userno:
1811
1891
  pgrec['userno'] = userno
1812
- elif pgrec['userno']:
1892
+ elif pgrec.get('userno'):
1813
1893
  pgrec['userno'] = userno = int(pgrec['userno'])
1814
- if pgrec['upid']: pgrec['upid'] = int(pgrec['upid'])
1815
- if pgrec['stat_flag']: pgrec['stat_flag'] = 'A' if pgrec['stat_flag'] == "True" else 'C'
1816
- if pgrec['email'] and re.search(r'(@|\.)ucar\.edu$', pgrec['email'], re.I):
1817
- pgrec['email'] = pgrec['ucaremail']
1894
+ if pgrec.get('upid'): pgrec['upid'] = int(pgrec['upid'])
1895
+ if pgrec.get('stat_flag'): pgrec['stat_flag'] = 'A' if pgrec['stat_flag'] == "True" else 'C'
1896
+ email = pgrec.get('email')
1897
+ if email and re.search(r'(@|\.)ucar\.edu$', email, re.I):
1898
+ email = pgrec['email'] = pgrec.get('ucaremail')
1818
1899
  pgrec['org_name'] = 'NCAR'
1819
- country = pgrec['country'] if 'country' in pgrec else None
1820
- pgrec['country'] = self.set_country_code(pgrec['email'], country)
1821
- if pgrec['division']:
1822
- val = "NCAR"
1823
- else:
1824
- val = None
1825
- pgrec['org_type'] = self.get_org_type(val, pgrec['email'])
1826
- buf = self.pgsystem("pgusername {}".format(pgrec['logname']), self.LOGWRN, 20)
1900
+ country = pgrec.get('country')
1901
+ if email or country: pgrec['country'] = self.set_country_code(email, country)
1902
+ val = "NCAR" if pgrec.get('division') else None
1903
+ pgrec['org_type'] = self.get_org_type(val, email)
1904
+ logname = pgrec.get('logname')
1905
+ if not logname or not self.valid_command("pgusername"): return pgrec
1906
+ buf = self.pgsystem("pgusername {}".format(logname), self.LOGWRN, 20)
1827
1907
  if not buf: return pgrec
1828
1908
  for line in buf.split('\n'):
1829
1909
  ms = re.match(r'^(.+)<=>(.*)$', line)
@@ -124,7 +124,8 @@ class PgLOG:
124
124
  'SETUID': '', # the login name for suid if it is different to the CURUID
125
125
  'FILEMODE': 0o664, # default 8-base file mode
126
126
  'EXECMODE': 0o775, # default 8-base executable file mode or directory mode
127
- # COMMONUSER and ADMINUSER are set below via SETPGLOG (env overrides PG<KEY>)
127
+ # COMMONUSER and ADMINUSER are set in set_common_pglog() via SETPGLOG,
128
+ # which lets environment variables COMMONUSER/ADMINUSER override the defaults
128
129
  'SUDOGDEX': 0, # 1 to allow sudo to self.PGLOG['COMMONUSER']
129
130
  'HOSTNAME': '', # current host name the process in running on
130
131
  'OBJCTSTR': "object",
@@ -183,13 +184,6 @@ class PgLOG:
183
184
  self.set_common_pglog()
184
185
  self.OUTPUT = None
185
186
 
186
- def SETPGLOG(self, key, default):
187
- """Set ``self.PGLOG[key]`` from environment variable ``PG<key>`` or
188
- fall back to ``default`` if the variable is unset. Used to make
189
- per-environment overrides (e.g. PGCOMMONUSER, PGADMINUSER) survive
190
- package upgrades."""
191
- self.PGLOG[key] = os.environ.get('PG' + key, default)
192
-
193
187
  def open_output(self, outfile=None):
194
188
  """Open the result output destination.
195
189
 
@@ -1435,7 +1429,7 @@ class PgLOG:
1435
1429
 
1436
1430
  Called automatically by :meth:`__init__`.
1437
1431
  """
1438
- # resolve common/admin user from environment (PGCOMMONUSER / PGADMINUSER)
1432
+ # resolve common/admin user from environment (COMMONUSER / ADMINUSER)
1439
1433
  self.SETPGLOG("COMMONUSER", "gdexdata")
1440
1434
  self.SETPGLOG("ADMINUSER", "zji")
1441
1435
  self.PGLOG['RDAUSER'] = self.PGLOG['COMMONUSER']
@@ -577,9 +577,9 @@ class PgOPT(PgFile):
577
577
  if 'LN' not in self.params: self.pglog("Could not get user login name", self.PGOPT['extlog'])
578
578
  self.validate_dataset()
579
579
  if self.OPTS[self.PGOPT['CACT']][2] > 0: self.validate_dsowner(aname)
580
- pgrec = self.pgget("dssdb.user", "uid", "logname = '{}' AND until_date IS NULL".format(self.params['LN']), self.PGOPT['extlog'])
581
- if not pgrec: self.pglog("Could not get user.uid for " + self.params['LN'], self.PGOPT['extlog'])
582
- self.PGOPT['UID'] = pgrec['uid']
580
+ uid = self.get_user_uid(self.params['LN'])
581
+ if not uid: self.pglog("Could not get user.uid for " + self.params['LN'], self.PGOPT['extlog'])
582
+ self.PGOPT['UID'] = uid
583
583
  self.open_output(self.params['OF'] if 'OF' in self.params else None)
584
584
 
585
585
  def set_sudo_uid(self, aname, uid):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.6
3
+ Version: 3.0.9
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