rda-python-dbms 2.0.9__py3-none-any.whl → 3.0.1__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.
@@ -22,6 +22,7 @@ class PgChksum(PgCMD, PgSplit):
22
22
  super().__init__() # initialize parent class
23
23
  self.PGSUM = {
24
24
  'd' : [], # delay mode option, value array
25
+ 'e' : 0, # 1 for emailing dataset owners the list of their missing files
25
26
  's' : [], # specialist login name array for email notice, default to none
26
27
  'f' : [], # file name array, default to none
27
28
  'i' : [], # dataset id array, default for all
@@ -46,6 +47,7 @@ class PgChksum(PgCMD, PgSplit):
46
47
  'begin' : 0,
47
48
  'end' : 0
48
49
  }
50
+ self.MISSING = {} # dsid => list of missing file names with their location
49
51
  self.pgname = "pgchksum"
50
52
  self.pgcmd = None
51
53
 
@@ -61,7 +63,7 @@ class PgChksum(PgCMD, PgSplit):
61
63
  self.PGLOG['BCKGRND'] = 1 # processing in backgroup mode
62
64
  option = None
63
65
  elif option in self.PGSUM:
64
- if option in ['m', 'n', 'x']:
66
+ if option in ['e', 'm', 'n', 'x']:
65
67
  self.PGSUM[option] = 1
66
68
  option = None
67
69
  else:
@@ -80,12 +82,13 @@ class PgChksum(PgCMD, PgSplit):
80
82
  else:
81
83
  self.pglog(arg + ": value passed without leading Option", self.LGEREX)
82
84
  if not self.PGSUM['t']:
83
- print("Usage: pgchksum -t (W|S|Q) [-b] [-m] [-n] [-x] [-c FileCount] \\")
85
+ print("Usage: pgchksum -t (W|S|Q) [-b] [-e] [-m] [-n] [-x] [-c FileCount] \\")
84
86
  print(" [-d [Hostnames] [RerunCount]] [-f FileNames] [-i DatasetIDs] \\")
85
87
  print(" [-k SkipCount] [-o r|s] [-s Specialists]")
86
88
  print(" -b - background process and no screen output")
87
89
  print(" -c - File count to re-evaluate checksum; for all gathered files if not specified")
88
90
  print(" -d - delayed mode background/batch processes on RDA machines/DAV PBS nodes")
91
+ print(" -e - email the primary owner of each dataset a reminder listing its missing files")
89
92
  print(" -f - Re-evaluate the specify File names only")
90
93
  print(" -i - Dataset IDs the files belong; for all files if not specified")
91
94
  print(" -k - skip the given file count to re-evaluate checksum")
@@ -100,6 +103,8 @@ class PgChksum(PgCMD, PgSplit):
100
103
  self.pgexit(0)
101
104
  elif self.PGSUM['t'] not in 'QSW':
102
105
  self.pglog(self.PGSUM['t'] + ": Unknown File Type, must be one of 'QSW'", self.LGEREX)
106
+ if self.PGSUM['e'] and self.PGSUM['n']:
107
+ self.pglog("Option -e (email owners) cannot work with -n (no email)", self.LGEREX)
103
108
  # set different log file
104
109
  self.PGLOG['LOGFILE'] = self.pgname + '.log'
105
110
  self.set_suid(self.PGLOG['EUID'])
@@ -151,6 +156,7 @@ class PgChksum(PgCMD, PgSplit):
151
156
  self.evaluate_backfile_checksum(fcnt, pgrecs, cnts)
152
157
  self.dump_progress(fcnt, fcnt, cnts, self.PVALS['emlsum'])
153
158
  self.PVALS['end'] = int(time.time())
159
+ if self.PGSUM['e'] and self.MISSING: self.send_owner_emails()
154
160
  if not self.PGSUM['n']: self.send_check_email(cnts['cnt'], fcnt)
155
161
  if self.PGLOG['DSCHECK']:
156
162
  if self.PGLOG['ERRMSG']:
@@ -173,6 +179,43 @@ class PgChksum(PgCMD, PgSplit):
173
179
  else:
174
180
  self.pglog(subject, self.LOGWRN|self.SNDEML)
175
181
 
182
+ # email the primary owner(s) of each dataset the list of its missing files
183
+ def send_owner_emails(self):
184
+ for dsid in sorted(self.MISSING):
185
+ files = self.MISSING[dsid]
186
+ fcnt = len(files)
187
+ s = 's' if fcnt > 1 else ''
188
+ tjoin = "dsowner LEFT JOIN dssgrp ON specialist = logname"
189
+ pgrecs = self.pgmget(tjoin, "specialist, fstname",
190
+ "dsid = '{}' AND priority = 1 ORDER BY specialist".format(dsid), self.PVALS['emlerr'])
191
+ if not pgrecs or not pgrecs['specialist']:
192
+ self.pglog("{}: No primary owner found in dsowner to email {} missing {} file{}".format(dsid, fcnt, self.PVALS['TYPE'], s), self.PVALS['emllog'])
193
+ continue
194
+ owners = []
195
+ names = []
196
+ for i in range(len(pgrecs['specialist'])):
197
+ owners.append(pgrecs['specialist'][i])
198
+ names.append(pgrecs['fstname'][i] if pgrecs['fstname'][i] else pgrecs['specialist'][i])
199
+ subject = "PGCHKSUM: {} {} File{} Missing for {}".format(fcnt, self.PVALS['TYPE'], s, dsid)
200
+ msg = "Dear {},\n\n".format(' and '.join(names))
201
+ msg += ("Checking {} files of dataset {}, {} found the following {} file{} recorded in RDADB " +
202
+ "but missing from the archive location{}. Please restore the file{}, or remove/update " +
203
+ "the file record{} via dsarch if no longer needed:\n\n").format(
204
+ self.PVALS['TYPE'], dsid, self.pgname, fcnt, s, s, s, s)
205
+ for i in range(fcnt):
206
+ msg += "{}. {}\n".format(i+1, files[i])
207
+ msg += "\n{} on {}\n".format(self.pgcmd, self.PGLOG['HOSTNAME'])
208
+ receiver = ', '.join(o if o.find('@') > -1 else o + "@ucar.edu" for o in owners)
209
+ if self.send_python_email(subject, receiver, msg, None, '', self.PVALS['emlerr']):
210
+ self.pglog("{}: emailed {} about {} missing {} file{}".format(dsid, receiver, fcnt, self.PVALS['TYPE'], s), self.PVALS['emllog'])
211
+
212
+ # record a missing file for the dataset owner email
213
+ def add_missing_file(self, fname, pgrec, location):
214
+ if not self.PGSUM['e']: return
215
+ dsid = pgrec['dsid']
216
+ if dsid not in self.MISSING: self.MISSING[dsid] = []
217
+ self.MISSING[dsid].append("{} (Type {}, {})".format(fname, pgrec['type'], location))
218
+
176
219
  # get the checksum filelist for wfile
177
220
  def get_checksum_wfilelist(self):
178
221
  self.PVALS['TYPE'] = "Web"
@@ -197,7 +240,7 @@ class PgChksum(PgCMD, PgSplit):
197
240
  if self.PGLOG['DSCHECK']: self.set_dscheck_attribute("dsid", dsid, self.PVALS['emlerr'])
198
241
  if self.PGSUM['m']: self.PVALS['CND'] += "(checksum IS NULL OR checksum < ' ') "
199
242
  elif dscnt > 1:
200
- self.PVALS['CND'] = "dsid IN ({}) ".format(','.join(self.PGSUM['i']))
243
+ self.PVALS['CND'] = "dsid IN ('{}') ".format("','".join(self.PGSUM['i']))
201
244
  if self.PGSUM['o']: self.PVALS['CND'] += "ORDER BY {} ".format('wid' if self.PGSUM['o'] == 's' else "RANDOM()")
202
245
  if fcnt: self.PVALS['CND'] += "LIMIT {} ".format(fcnt)
203
246
  if self.PGSUM['k']:
@@ -206,6 +249,7 @@ class PgChksum(PgCMD, PgSplit):
206
249
  if dscnt == 1:
207
250
  wfrecs = self.pgmget_wfile(dsid, flds, self.PVALS['CND'])
208
251
  cnt = len(wfrecs['wid']) if wfrecs else 0
252
+ if cnt > 0: wfrecs['dsid'] = [dsid]*cnt # wfile_{dsid} has no dsid column
209
253
  else:
210
254
  pgrecs = self.pgmget('wfile', 'wid, dsid', self.PVALS['CND'])
211
255
  cnt = len(pgrecs['wid']) if pgrecs else 0
@@ -245,7 +289,7 @@ class PgChksum(PgCMD, PgSplit):
245
289
  self.PVALS['CND'] = "dsid = '{}'".format(dsid)
246
290
  if self.PGLOG['DSCHECK']: self.set_dscheck_attribute("dsid", dsid, self.PVALS['emlerr'])
247
291
  elif cnt > 1:
248
- self.PVALS['CND'] = "dsid IN ({})".format(','.join(self.PGSUM['i']))
292
+ self.PVALS['CND'] = "dsid IN ('{}')".format("','".join(self.PGSUM['i']))
249
293
  if self.PGSUM['m']:
250
294
  if self.PVALS['CND']: self.PVALS['CND'] += " AND "
251
295
  self.PVALS['CND'] += "(checksum IS NULL OR checksum < ' ')"
@@ -279,7 +323,7 @@ class PgChksum(PgCMD, PgSplit):
279
323
  self.PVALS['CND'] = "dsid = '{}' AND ".format(dsid)
280
324
  if self.PGLOG['DSCHECK']: self.set_dscheck_attribute("dsid", dsid, self.PVALS['emlerr'])
281
325
  elif cnt > 1:
282
- self.PVALS['CND'] = "dsid IN ({}) AND ".format(','.join(self.PGSUM['i']))
326
+ self.PVALS['CND'] = "dsid IN ('{}') AND ".format("','".join(self.PGSUM['i']))
283
327
  if self.PGSUM['m']:
284
328
  self.PVALS['CND'] += "(checksum IS NULL OR checksum < ' ') AND "
285
329
  self.PVALS['CND'] += "status = 'A'"
@@ -376,6 +420,7 @@ class PgChksum(PgCMD, PgSplit):
376
420
  self.evaluate_file_status('DSK', fname, pgrec, info, cnts)
377
421
  else:
378
422
  self.pglog("{}: Type {} {} file is missing on Disk".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
423
+ self.add_missing_file(fname, pgrec, 'on Disk')
379
424
  cnts['ecnt'] += 1
380
425
 
381
426
  # evaluate an object file
@@ -385,6 +430,7 @@ class PgChksum(PgCMD, PgSplit):
385
430
  self.evaluate_file_status('OBS', "{}-{}".format(bucket, fname), pgrec, info, cnts)
386
431
  else:
387
432
  self.pglog("{}: Type {} {} file is missing in Object Store".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
433
+ self.add_missing_file("{}-{}".format(bucket, fname), pgrec, 'in Object Store')
388
434
  cnts['ecnt'] += 1
389
435
 
390
436
  # evaluate a quasar file
@@ -394,6 +440,7 @@ class PgChksum(PgCMD, PgSplit):
394
440
  self.evaluate_file_status('QSR', "{}-{}".format(endpoint, fname), pgrec, info, cnts)
395
441
  else:
396
442
  self.pglog("{}: Type {} {} file is missing on Quasar".format(fname, pgrec['type'], self.PVALS['TYPE']), self.PVALS['emllog'])
443
+ self.add_missing_file("{}-{}".format(endpoint, fname), pgrec, 'on Quasar')
397
444
  cnts['ecnt'] += 1
398
445
 
399
446
  # evaluate a file status
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: rda_python_dbms
3
+ Version: 3.0.1
4
+ Summary: RDA Python Package to manage PostreSQL databases
5
+ Author-email: Zaihua Ji <zji@ucar.edu>
6
+ Project-URL: Homepage, https://github.com/NCAR/rda-python-dbms
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: rda_python_common
15
+ Dynamic: license-file
16
+
17
+ # rda_python_dbms
18
+
19
+ RDA Python package to manage RDA PostgreSQL databases.
20
+
21
+ ## Programs
22
+
23
+ The package installs the following command-line utilities, all running as the
24
+ current user:
25
+
26
+ - `pgddl` — create, drop or alter tables, indexes and keys from the table
27
+ definitions in `<tablename>.json` files
28
+ - `pgseq` — reset the sequences of all (or the given) tables in one or more
29
+ schemas
30
+ - `pgbackup` — dump the configured `rdadb` schemas on `rda-pgdb-02` into
31
+ `/data/pgbackup`, replacing existing backups and emailing a summary
32
+ - `pgdbdump` — dump a PostgreSQL database to a local directory
33
+ - `pgdbrestore` — restore a PostgreSQL database from a local dump directory
34
+ - `pgchksum` — re-check archived files against their RDADB records
35
+ (checksums, sizes and timestamps for Web and Saved files; sizes and
36
+ timestamps only for Quasar backup files), optionally fix the records
37
+ (`-x`), and email each dataset's primary owner a list of its missing
38
+ files (`-e`)
39
+ - `pgcmptables` — compare the columns, indexes and data of identical tables
40
+ on two database servers
41
+ - `pgschema` — copy the tables of a schema into the same schema in another
42
+ database on the same server
43
+
44
+ Run a program without its required options to print its usage. Do not use
45
+ `-h` for help: it is the host name option in several programs. `pgbackup`
46
+ has no usage text and starts the backup as soon as it runs.
47
+
48
+ ## Environment setup
49
+
50
+ Create a Python environment first; package installs in the next section run
51
+ inside whichever environment you activate here.
52
+
53
+ ### Option A — Python venv (DECS machines)
54
+
55
+ ```bash
56
+ python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
57
+ source $ENVHOME/bin/activate
58
+ ```
59
+
60
+ ### Option B — Conda (DAV/Casper)
61
+
62
+ ```bash
63
+ conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
64
+ conda activate $ENVHOME
65
+ ```
66
+
67
+ ## Installing rda-python-dbms
68
+
69
+ Pick whichever install mode fits your workflow. All variants pull in the
70
+ transitive dependency (`rda_python_common`) automatically.
71
+
72
+ For local development, clone this repo alongside your project and install it
73
+ in editable mode so that changes are picked up without re-installing:
74
+
75
+ ```bash
76
+ git clone https://github.com/NCAR/rda-python-dbms.git
77
+ cd rda-python-dbms
78
+ pip install -e .
79
+ ```
80
+
81
+ To test a specific branch (e.g. an in-progress feature or fix branch), pass
82
+ `-b/--branch` to `git clone`:
83
+
84
+ ```bash
85
+ git clone -b <branch-name> https://github.com/NCAR/rda-python-dbms.git
86
+ cd rda-python-dbms
87
+ pip install -e .
88
+ ```
89
+
90
+ For a regular (non-editable) install from a checkout:
91
+
92
+ ```bash
93
+ pip install /path/to/rda-python-dbms
94
+ ```
95
+
96
+ For a production install on a system that uses the published distribution:
97
+
98
+ ```bash
99
+ pip install rda_python_dbms
100
+ ```
@@ -10,7 +10,7 @@ rda_python_dbms/pg_ddllib.py,sha256=-zawllfUCe7QZNvza-u5TAYKP4yilC9K6TetHhoGenU,
10
10
  rda_python_dbms/pg_schema.py,sha256=Vsf5fKENYIOgMKE3yS0TkXhg-aeHZ2nYzpvltFAcdFY,5520
11
11
  rda_python_dbms/pg_seq.py,sha256=wNxqsyIjcTplJErPirZ-VkZ8HS4mgQUwPz8Xwd12kYA,4914
12
12
  rda_python_dbms/pgbackup.py,sha256=IuP3nQEEVR86YlSZuGyqsdz1-O9rgET76FnWB7iL3xk,4249
13
- rda_python_dbms/pgchksum.py,sha256=8jfBncptxp57Gr2t66Kk9MFt5Z_aXfozOGZPaohKGwM,21802
13
+ rda_python_dbms/pgchksum.py,sha256=ePHdh2MQ3q8102Nm7znXPg8ToI-ohGnyg3f-ipIYWu8,24921
14
14
  rda_python_dbms/pgcmptables.py,sha256=LQ4KK4JuQs2z3nBdINWYgyiGu2MF9RZjS5_cAktbs6k,47123
15
15
  rda_python_dbms/pgdbdump.py,sha256=8d-1z16Y2DyJnQqex2wFu5svChKgncPnzdQ-iUftHg8,3831
16
16
  rda_python_dbms/pgdbrestore.py,sha256=d12hl1r6usK1oGIFAPVWUB_KkIH04PPlugxVSnLOX1g,3110
@@ -954,9 +954,9 @@ rda_python_dbms/tables/obsua/wind_5v2_0.json,sha256=NehMFE-GMNlD5Bh_fiacfSOmZWkr
954
954
  rda_python_dbms/tables/obsua/wind_60v2_0.json,sha256=jOyQgD5ph7YBMuQfvYz1Ln6X_-_Vdhl36HBexOHZ1dA,1067
955
955
  rda_python_dbms/tables/obsua/wind_8v2_0.json,sha256=5wtCThmFeOyiAGH_Qv3_jrXjhjo9b87ehlpI9QQY2SM,1064
956
956
  rda_python_dbms/tables/obsua/wind_9v2_0.json,sha256=7OKFUicOn15LL_WuF1VE_L8DjxsHmRP4uBnopuMIeTg,1064
957
- rda_python_dbms-2.0.9.dist-info/licenses/LICENSE,sha256=Uky6fiiVw7cakFVRtVpzg-mvqOQ-WcA_H8lHqhPRg58,1101
958
- rda_python_dbms-2.0.9.dist-info/METADATA,sha256=8jhKTRIo91_CXYgIdyMBwXbCZQ1VOpS-1ghgC6NxVbI,617
959
- rda_python_dbms-2.0.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
960
- rda_python_dbms-2.0.9.dist-info/entry_points.txt,sha256=iti6iZS2OEeBqO5aHlWeMFcbuwN_2assidP0qeSkQ6E,346
961
- rda_python_dbms-2.0.9.dist-info/top_level.txt,sha256=4T1ShNUMyiqmwDIuDL7pDOm_S-6zWWW3Vy6OdAzYA4c,16
962
- rda_python_dbms-2.0.9.dist-info/RECORD,,
957
+ rda_python_dbms-3.0.1.dist-info/licenses/LICENSE,sha256=Uky6fiiVw7cakFVRtVpzg-mvqOQ-WcA_H8lHqhPRg58,1101
958
+ rda_python_dbms-3.0.1.dist-info/METADATA,sha256=iqXoJ9_l_fP-i2prZLDDLZnXx9Cvko9VL2OCEXz1kAI,3293
959
+ rda_python_dbms-3.0.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
960
+ rda_python_dbms-3.0.1.dist-info/entry_points.txt,sha256=iti6iZS2OEeBqO5aHlWeMFcbuwN_2assidP0qeSkQ6E,346
961
+ rda_python_dbms-3.0.1.dist-info/top_level.txt,sha256=4T1ShNUMyiqmwDIuDL7pDOm_S-6zWWW3Vy6OdAzYA4c,16
962
+ rda_python_dbms-3.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rda_python_dbms
3
- Version: 2.0.9
4
- Summary: RDA Python Package to manage PostreSQL databases
5
- Author-email: Zaihua Ji <zji@ucar.edu>
6
- Project-URL: Homepage, https://github.com/NCAR/rda-python-dbms
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: rda_python_common
15
- Dynamic: license-file
16
-
17
- RDA python Package to manage RDA PostgreSQL databases.