rda-python-common 3.0.25__tar.gz → 3.0.26__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.25/src/rda_python_common.egg-info → rda_python_common-3.0.26}/PKG-INFO +2 -2
  2. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/README.md +1 -1
  3. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/pyproject.toml +1 -1
  4. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_file.py +45 -13
  6. {rda_python_common-3.0.25 → rda_python_common-3.0.26/src/rda_python_common.egg-info}/PKG-INFO +2 -2
  7. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/LICENSE +0 -0
  8. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/setup.cfg +0 -0
  9. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgCMD.py +0 -0
  10. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgDBI.py +0 -0
  11. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgFile.py +0 -0
  12. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgLOG.py +0 -0
  13. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgLock.py +0 -0
  14. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgOPT.py +0 -0
  15. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgSIG.py +0 -0
  16. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgSplit.py +0 -0
  17. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/PgUtil.py +0 -0
  18. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_cmd.py +0 -0
  19. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_dbi.py +0 -0
  20. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_lock.py +0 -0
  21. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_log.py +0 -0
  22. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_opt.py +0 -0
  23. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_password.py +0 -0
  24. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_sig.py +0 -0
  25. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.25 → rda_python_common-3.0.26}/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.25
3
+ Version: 3.0.26
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.25`). If the import
192
+ You should see the installed version (currently `3.0.26`). 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.25`). If the import
168
+ You should see the installed version (currently `3.0.26`). 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.25"
7
+ version = "3.0.26"
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.25"
25
+ __version__ = "3.0.26"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -794,7 +794,7 @@ class PgFile(PgUtil, PgSIG):
794
794
  # tofile - target file name
795
795
  # fromfile - source file name
796
796
  # bucket - bucket name on Object store
797
- def object_copy_local(self, tofile, fromfile, bucket = None, logact = 0):
797
+ def object_copy_local(self, tofile, fromfile, bucket = None, logact = 0, finfo = None):
798
798
  """Download a file from the object store to the local filesystem.
799
799
 
800
800
  Changes to the target directory, downloads using isd_s3_cli, verifies size,
@@ -807,31 +807,42 @@ class PgFile(PgUtil, PgSIG):
807
807
  fromfile (str): Object key (source path in the bucket).
808
808
  bucket (str | None): Source bucket; defaults to PGLOG['OBJCTBKT'].
809
809
  logact (int): Logging action flags; default 0.
810
+ finfo (dict | None): check_object_file() info for fromfile, from a call
811
+ with opt bit 1, when the caller already has it;
812
+ saves listing the object a second time.
810
813
 
811
814
  Returns:
812
815
  int: self.SUCCESS on success, self.FAILURE on error.
813
816
  """
814
817
  ret = self.FAILURE
815
818
  if not bucket: bucket = self.PGLOG['OBJCTBKT']
816
- finfo = self.check_object_file(fromfile, bucket, 0, logact)
819
+ # opt 1 adds the LastModified the 'lo' listing already carries, which
820
+ # object_get_local needs to tell whether tofile is staged already
821
+ if not finfo: finfo = self.check_object_file(fromfile, bucket, 1, logact)
817
822
  if not finfo:
818
823
  if finfo != None: return ret
819
824
  return self.lmsg(fromfile, "{}-{} to copy to {}".format(self.OHOST, self.PGLOG['MISSFILE'], tofile), logact)
820
- if not finfo['isfile']: return self.object_copy_local_directory(tofile, fromfile, bucket, logact)
821
- return self.object_get_local(tofile, fromfile, finfo['data_size'], bucket, logact)
825
+ if not finfo['isfile']:
826
+ return self.object_copy_local_directory(tofile, fromfile, bucket, logact, finfo.get('flist'))
827
+ return self.object_get_local(tofile, fromfile, finfo, bucket, logact)
822
828
 
823
829
  # Download a single object of a known size to a local file
824
830
  # tofile - target file name
825
831
  # fromfile - source object key name
826
- # fsize - size of the object, to verify the download against
832
+ # finfo - check_object_file() info of the object, to verify the download against
827
833
  # bucket - bucket name on Object store
828
- def object_get_local(self, tofile, fromfile, fsize, bucket, logact = 0):
834
+ def object_get_local(self, tofile, fromfile, finfo, bucket, logact = 0):
829
835
  """Download one object key to a local file and verify its size.
830
836
 
837
+ Nothing is fetched when tofile is already staged at the object's size and
838
+ no older than the object itself, unless logact carries OVRIDE, so the
839
+ second copy into the same file (dsarch makes one when it tars) is free.
840
+
831
841
  Args:
832
842
  tofile (str): Destination local file path.
833
843
  fromfile (str): Object key (source path in the bucket).
834
- fsize (int): Expected size of the object.
844
+ finfo (dict): check_object_file() info of the object; 'data_size' is
845
+ required, 'date_modified'/'time_modified' enable the skip.
835
846
  bucket (str): Source bucket.
836
847
  logact (int): Logging action flags; default 0.
837
848
 
@@ -839,6 +850,13 @@ class PgFile(PgUtil, PgSIG):
839
850
  int: self.SUCCESS on success, self.FAILURE on error.
840
851
  """
841
852
  ret = self.FAILURE
853
+ fsize = finfo['data_size']
854
+ if not logact&self.OVRIDE and 'date_modified' in finfo:
855
+ linfo = self.check_local_file(tofile, 1, logact)
856
+ if linfo and linfo['isfile'] and linfo['data_size'] == fsize and \
857
+ self.cmptime(linfo['date_modified'], linfo['time_modified'],
858
+ finfo['date_modified'], finfo['time_modified']) >= 0:
859
+ return self.SUCCESS
842
860
  ocmd = self.OBJCTCMD
843
861
  cmd = "{} go -k {} -b {}".format(ocmd, fromfile, bucket)
844
862
  fromname = op.basename(fromfile)
@@ -871,7 +889,8 @@ class PgFile(PgUtil, PgSIG):
871
889
  # todir - target local directory name
872
890
  # fromdir - source object key prefix
873
891
  # bucket - bucket name on Object store
874
- def object_copy_local_directory(self, todir, fromdir, bucket = None, logact = 0):
892
+ # flist - member listing from check_object_file(), if the caller has it
893
+ def object_copy_local_directory(self, todir, fromdir, bucket = None, logact = 0, flist = None):
875
894
  """Download every object under a key prefix into a local directory.
876
895
 
877
896
  The object store has no directories; a zarr store and the like is a set of
@@ -889,6 +908,9 @@ class PgFile(PgUtil, PgSIG):
889
908
  fromdir (str): Object key prefix (source path in the bucket).
890
909
  bucket (str | None): Source bucket; defaults to PGLOG['OBJCTBKT'].
891
910
  logact (int): Logging action flags; default 0.
911
+ flist (dict | None): Mapping of key → object info with LastModified, as
912
+ check_object_file() keeps it for a key prefix;
913
+ listed here when not given.
892
914
 
893
915
  Returns:
894
916
  int: self.SUCCESS on success, self.FAILURE on error.
@@ -896,10 +918,11 @@ class PgFile(PgUtil, PgSIG):
896
918
  if not bucket: bucket = self.PGLOG['OBJCTBKT']
897
919
  ms = re.match(r'^(.+)/$', fromdir)
898
920
  if ms: fromdir = ms.group(1)
899
- # opt 1 adds the LastModified the 'lo' listing already carries; it is only
900
- # bits 2 and 8 that would cost a metadata call per key
901
- flist = self.object_glob(fromdir, bucket, 1, logact)
902
- if flist == self.FAILURE: return self.FAILURE
921
+ if flist is None:
922
+ # opt 1 adds the LastModified the 'lo' listing already carries; it is only
923
+ # bits 2 and 8 that would cost a metadata call per key
924
+ flist = self.object_glob(fromdir, bucket, 1, logact)
925
+ if flist == self.FAILURE: return self.FAILURE
903
926
  prefix = fromdir + '/'
904
927
  keys = [key for key in flist if key.startswith(prefix)]
905
928
  if not keys:
@@ -2452,6 +2475,8 @@ class PgFile(PgUtil, PgSIG):
2452
2475
 
2453
2476
  Strips trailing '/'. Uses opt bit 64 to skip the retry. Fetches metadata
2454
2477
  (uhash) when opt includes bits 2, 4, or 8. Resets ECNTS['O'] on success.
2478
+ Only the key itself, or the keys under it as a directory, count as a
2479
+ match; other keys that merely start with the same characters are ignored.
2455
2480
 
2456
2481
  Args:
2457
2482
  file (str): Object key to check.
@@ -2477,7 +2502,11 @@ class PgFile(PgUtil, PgSIG):
2477
2502
  if buf:
2478
2503
  if re.match(r'^\[\]', buf): break
2479
2504
  if re.match(r'^\[\{', buf):
2480
- ary = json.loads(buf)
2505
+ # 'lo' matches by key prefix, so x.zarr also lists a sibling x.zarr2/...
2506
+ # and a.nc lists a.nc.bak; keep only the key itself and keys under it
2507
+ prefix = file + '/'
2508
+ ary = [a for a in json.loads(buf) if a['Key'] == file or a['Key'].startswith(prefix)]
2509
+ if not ary: break
2481
2510
  cnt = len(ary)
2482
2511
  isdir = (cnt > 1 or ary[0]['Key'] != file)
2483
2512
  # a key prefix has no timestamp of its own; report the newest member's
@@ -2495,6 +2524,9 @@ class PgFile(PgUtil, PgSIG):
2495
2524
  for a in ary:
2496
2525
  size += int(a['Size'])
2497
2526
  ret['data_size'] = size
2527
+ # keep the member listing, as object_glob(file, bucket, 1) builds it,
2528
+ # so a download of the prefix does not list it a second time
2529
+ ret['flist'] = {a['Key']: self.object_file_stat(a, None, 1) for a in ary}
2498
2530
  uhash = None
2499
2531
  break
2500
2532
  if opt&64: return self.FAILURE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.25
3
+ Version: 3.0.26
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.25`). If the import
192
+ You should see the installed version (currently `3.0.26`). 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