rda-python-common 3.0.23__tar.gz → 3.0.25__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.23/src/rda_python_common.egg-info → rda_python_common-3.0.25}/PKG-INFO +2 -2
  2. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/README.md +1 -1
  3. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/pyproject.toml +1 -1
  4. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/__init__.py +1 -1
  5. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_file.py +94 -5
  6. {rda_python_common-3.0.23 → rda_python_common-3.0.25/src/rda_python_common.egg-info}/PKG-INFO +2 -2
  7. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/LICENSE +0 -0
  8. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/setup.cfg +0 -0
  9. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgCMD.py +0 -0
  10. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgDBI.py +0 -0
  11. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgFile.py +0 -0
  12. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgLOG.py +0 -0
  13. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgLock.py +0 -0
  14. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgOPT.py +0 -0
  15. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgSIG.py +0 -0
  16. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgSplit.py +0 -0
  17. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/PgUtil.py +0 -0
  18. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_cmd.py +0 -0
  19. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_dbi.py +0 -0
  20. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_lock.py +0 -0
  21. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_log.py +0 -0
  22. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_opt.py +0 -0
  23. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_password.py +0 -0
  24. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_sig.py +0 -0
  25. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_split.py +0 -0
  26. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pg_util.py +0 -0
  27. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pgpassword.py +0 -0
  28. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common/pgpassword.usg +0 -0
  29. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
  30. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
  31. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common.egg-info/entry_points.txt +0 -0
  32. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common.egg-info/requires.txt +0 -0
  33. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/src/rda_python_common.egg-info/top_level.txt +0 -0
  34. {rda_python_common-3.0.23 → rda_python_common-3.0.25}/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.23
3
+ Version: 3.0.25
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.23`). If the import
192
+ You should see the installed version (currently `3.0.25`). 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.23`). If the import
168
+ You should see the installed version (currently `3.0.25`). 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.23"
7
+ version = "3.0.25"
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.23"
25
+ __version__ = "3.0.25"
26
26
 
27
27
  __all__ = [
28
28
  "PgLOG",
@@ -875,8 +875,14 @@ class PgFile(PgUtil, PgSIG):
875
875
  """Download every object under a key prefix into a local directory.
876
876
 
877
877
  The object store has no directories; a zarr store and the like is a set of
878
- objects sharing a key prefix. Each object is downloaded individually and
879
- keeps its position relative to the prefix under todir.
878
+ objects sharing a key prefix. The whole prefix comes down in one recursive
879
+ call, which is what makes this usable at all: such a store routinely holds
880
+ thousands of tiny members, and one process per member costs hours where
881
+ the recursive call costs seconds.
882
+
883
+ Nothing is fetched when every member is already staged under todir at the
884
+ object's size and no older than the object itself, so the second copy into
885
+ the same directory (dsarch makes one when it tars) is free.
880
886
 
881
887
  Args:
882
888
  todir (str): Destination local directory path.
@@ -890,17 +896,100 @@ class PgFile(PgUtil, PgSIG):
890
896
  if not bucket: bucket = self.PGLOG['OBJCTBKT']
891
897
  ms = re.match(r'^(.+)/$', fromdir)
892
898
  if ms: fromdir = ms.group(1)
893
- flist = self.object_glob(fromdir, bucket, 0, logact)
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)
894
902
  if flist == self.FAILURE: return self.FAILURE
895
903
  prefix = fromdir + '/'
896
904
  keys = [key for key in flist if key.startswith(prefix)]
897
905
  if not keys:
898
906
  return self.lmsg(fromdir, "{}-{} to copy to {}".format(self.OHOST, self.PGLOG['MISSFILE'], todir), logact)
907
+ # a partly staged directory is downloaded again in full rather than member by
908
+ # member: one member costs about as much as the whole recursive call, so
909
+ # fetching even a handful individually is the slower way round
910
+ if not self.object_local_missing(todir, prefix, keys, flist, 1, logact):
911
+ # every member is current, but the key list is the authoritative one: a local
912
+ # file no longer under the prefix means the store was rewritten in place, and
913
+ # archiving it would keep members the store has dropped. start over instead
914
+ extra = self.local_extra_files(todir, prefix, keys)
915
+ if not extra: return self.SUCCESS
916
+ self.pglog("{}: restage over {} file(s) no longer in {}-{}".format(
917
+ todir, len(extra), self.OHOST, fromdir), logact|self.LOGWRN)
918
+ if not self.delete_local_file(todir, logact): return self.FAILURE
919
+ self.make_local_directory(todir, logact)
920
+ cmd = "{} go -k {} -b {} -ld {} -r".format(self.OBJCTCMD, fromdir, bucket, todir)
921
+ buf = self.pgsystem(cmd, logact, self.CMDBTH)
922
+ # verify on size alone; a member just downloaded is newer than the object by
923
+ # definition, and comparing timestamps here would only expose a clock skew
924
+ miss = self.object_local_missing(todir, prefix, keys, flist, 0, logact)
925
+ if miss:
926
+ return self.errlog("{}-{}: Error download {} of {} member(s) to {}\n{}".format(
927
+ self.OHOST, fromdir, len(miss), len(keys), todir, buf), 'O', 1, logact)
928
+ return self.SUCCESS
929
+
930
+ # List the members of an object key prefix that are not staged under todir yet
931
+ # todir - target local directory name
932
+ # prefix - source object key prefix, including the trailing '/'
933
+ # keys - the member keys to check
934
+ # flist - object_glob() info, keyed by member key
935
+ # chktime - also require the local copy to be no older than the object
936
+ def object_local_missing(self, todir, prefix, keys, flist, chktime = 0, logact = 0):
937
+ """Return the member keys that still need downloading under a key prefix.
938
+
939
+ A local copy counts as present when it matches the object size, and with
940
+ chktime also when it is no older than the object's LastModified.
941
+
942
+ Args:
943
+ todir (str): Destination local directory path.
944
+ prefix (str): Object key prefix including the trailing '/'.
945
+ keys (list): Member keys to check.
946
+ flist (dict): Mapping of key → object info from object_glob().
947
+ chktime (int): Also compare modification times; default 0.
948
+ logact (int): Logging action flags; default 0.
949
+
950
+ Returns:
951
+ list: The member keys not staged under todir.
952
+ """
953
+ miss = []
899
954
  plen = len(prefix)
900
955
  for key in keys:
956
+ oinfo = flist[key]
901
957
  tofile = "{}/{}".format(todir, key[plen:])
902
- if not self.object_get_local(tofile, key, flist[key]['data_size'], bucket, logact): return self.FAILURE
903
- return self.SUCCESS
958
+ linfo = self.check_local_file(tofile, 1, logact)
959
+ if linfo and linfo['data_size'] == oinfo['data_size']:
960
+ if not chktime: continue
961
+ # an unparsable LastModified leaves date_modified unset; fetch again then
962
+ if 'date_modified' in oinfo and \
963
+ self.cmptime(linfo['date_modified'], linfo['time_modified'],
964
+ oinfo['date_modified'], oinfo['time_modified']) >= 0: continue
965
+ miss.append(key)
966
+ return miss
967
+
968
+ # List the files staged under todir that the object key prefix no longer holds
969
+ # todir - target local directory name
970
+ # prefix - source object key prefix, including the trailing '/'
971
+ # keys - the member keys the object store currently holds
972
+ def local_extra_files(self, todir, prefix, keys):
973
+ """Return the paths under todir that are not members of an object key prefix.
974
+
975
+ Paths are relative to todir, matching the layout a recursive download creates.
976
+
977
+ Args:
978
+ todir (str): Local directory holding the staged copy.
979
+ prefix (str): Object key prefix including the trailing '/'.
980
+ keys (list): The member keys currently under the prefix.
981
+
982
+ Returns:
983
+ list: Relative paths under todir with no matching member key.
984
+ """
985
+ plen = len(prefix)
986
+ members = set(key[plen:] for key in keys)
987
+ extra = []
988
+ for path, dirs, files in os.walk(todir):
989
+ for fname in files:
990
+ name = op.relpath(op.join(path, fname), todir)
991
+ if name not in members: extra.append(name)
992
+ return extra
904
993
 
905
994
  # Copy a remote file to object
906
995
  # tofile - target object file name
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_common
3
- Version: 3.0.23
3
+ Version: 3.0.25
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.23`). If the import
192
+ You should see the installed version (currently `3.0.25`). 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