rda-python-common 3.0.24__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.
- {rda_python_common-3.0.24/src/rda_python_common.egg-info → rda_python_common-3.0.26}/PKG-INFO +2 -2
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/README.md +1 -1
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/pyproject.toml +1 -1
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/__init__.py +1 -1
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_file.py +133 -25
- {rda_python_common-3.0.24 → rda_python_common-3.0.26/src/rda_python_common.egg-info}/PKG-INFO +2 -2
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/LICENSE +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/setup.cfg +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgCMD.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgDBI.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgFile.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgLOG.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgLock.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgOPT.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgSIG.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgSplit.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/PgUtil.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_cmd.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_dbi.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_lock.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_log.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_opt.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_password.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_sig.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_split.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pg_util.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pgpassword.py +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common/pgpassword.usg +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/SOURCES.txt +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/dependency_links.txt +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/entry_points.txt +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/requires.txt +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/top_level.txt +0 -0
- {rda_python_common-3.0.24 → rda_python_common-3.0.26}/test/test_common.py +0 -0
{rda_python_common-3.0.24/src/rda_python_common.egg-info → rda_python_common-3.0.26}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_common
|
|
3
|
-
Version: 3.0.
|
|
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.
|
|
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.
|
|
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
|
|
|
@@ -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
|
-
|
|
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']:
|
|
821
|
-
|
|
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
|
-
#
|
|
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,
|
|
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
|
-
|
|
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,23 +889,28 @@ 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
|
-
|
|
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
|
|
878
|
-
objects sharing a key prefix.
|
|
879
|
-
|
|
897
|
+
objects sharing a key prefix. The whole prefix comes down in one recursive
|
|
898
|
+
call, which is what makes this usable at all: such a store routinely holds
|
|
899
|
+
thousands of tiny members, and one process per member costs hours where
|
|
900
|
+
the recursive call costs seconds.
|
|
880
901
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
over, and a second copy into the same directory costs nothing.
|
|
902
|
+
Nothing is fetched when every member is already staged under todir at the
|
|
903
|
+
object's size and no older than the object itself, so the second copy into
|
|
904
|
+
the same directory (dsarch makes one when it tars) is free.
|
|
885
905
|
|
|
886
906
|
Args:
|
|
887
907
|
todir (str): Destination local directory path.
|
|
888
908
|
fromdir (str): Object key prefix (source path in the bucket).
|
|
889
909
|
bucket (str | None): Source bucket; defaults to PGLOG['OBJCTBKT'].
|
|
890
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.
|
|
891
914
|
|
|
892
915
|
Returns:
|
|
893
916
|
int: self.SUCCESS on success, self.FAILURE on error.
|
|
@@ -895,25 +918,101 @@ class PgFile(PgUtil, PgSIG):
|
|
|
895
918
|
if not bucket: bucket = self.PGLOG['OBJCTBKT']
|
|
896
919
|
ms = re.match(r'^(.+)/$', fromdir)
|
|
897
920
|
if ms: fromdir = ms.group(1)
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
|
902
926
|
prefix = fromdir + '/'
|
|
903
927
|
keys = [key for key in flist if key.startswith(prefix)]
|
|
904
928
|
if not keys:
|
|
905
929
|
return self.lmsg(fromdir, "{}-{} to copy to {}".format(self.OHOST, self.PGLOG['MISSFILE'], todir), logact)
|
|
930
|
+
# a partly staged directory is downloaded again in full rather than member by
|
|
931
|
+
# member: one member costs about as much as the whole recursive call, so
|
|
932
|
+
# fetching even a handful individually is the slower way round
|
|
933
|
+
if not self.object_local_missing(todir, prefix, keys, flist, 1, logact):
|
|
934
|
+
# every member is current, but the key list is the authoritative one: a local
|
|
935
|
+
# file no longer under the prefix means the store was rewritten in place, and
|
|
936
|
+
# archiving it would keep members the store has dropped. start over instead
|
|
937
|
+
extra = self.local_extra_files(todir, prefix, keys)
|
|
938
|
+
if not extra: return self.SUCCESS
|
|
939
|
+
self.pglog("{}: restage over {} file(s) no longer in {}-{}".format(
|
|
940
|
+
todir, len(extra), self.OHOST, fromdir), logact|self.LOGWRN)
|
|
941
|
+
if not self.delete_local_file(todir, logact): return self.FAILURE
|
|
942
|
+
self.make_local_directory(todir, logact)
|
|
943
|
+
cmd = "{} go -k {} -b {} -ld {} -r".format(self.OBJCTCMD, fromdir, bucket, todir)
|
|
944
|
+
buf = self.pgsystem(cmd, logact, self.CMDBTH)
|
|
945
|
+
# verify on size alone; a member just downloaded is newer than the object by
|
|
946
|
+
# definition, and comparing timestamps here would only expose a clock skew
|
|
947
|
+
miss = self.object_local_missing(todir, prefix, keys, flist, 0, logact)
|
|
948
|
+
if miss:
|
|
949
|
+
return self.errlog("{}-{}: Error download {} of {} member(s) to {}\n{}".format(
|
|
950
|
+
self.OHOST, fromdir, len(miss), len(keys), todir, buf), 'O', 1, logact)
|
|
951
|
+
return self.SUCCESS
|
|
952
|
+
|
|
953
|
+
# List the members of an object key prefix that are not staged under todir yet
|
|
954
|
+
# todir - target local directory name
|
|
955
|
+
# prefix - source object key prefix, including the trailing '/'
|
|
956
|
+
# keys - the member keys to check
|
|
957
|
+
# flist - object_glob() info, keyed by member key
|
|
958
|
+
# chktime - also require the local copy to be no older than the object
|
|
959
|
+
def object_local_missing(self, todir, prefix, keys, flist, chktime = 0, logact = 0):
|
|
960
|
+
"""Return the member keys that still need downloading under a key prefix.
|
|
961
|
+
|
|
962
|
+
A local copy counts as present when it matches the object size, and with
|
|
963
|
+
chktime also when it is no older than the object's LastModified.
|
|
964
|
+
|
|
965
|
+
Args:
|
|
966
|
+
todir (str): Destination local directory path.
|
|
967
|
+
prefix (str): Object key prefix including the trailing '/'.
|
|
968
|
+
keys (list): Member keys to check.
|
|
969
|
+
flist (dict): Mapping of key → object info from object_glob().
|
|
970
|
+
chktime (int): Also compare modification times; default 0.
|
|
971
|
+
logact (int): Logging action flags; default 0.
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
list: The member keys not staged under todir.
|
|
975
|
+
"""
|
|
976
|
+
miss = []
|
|
906
977
|
plen = len(prefix)
|
|
907
978
|
for key in keys:
|
|
908
979
|
oinfo = flist[key]
|
|
909
980
|
tofile = "{}/{}".format(todir, key[plen:])
|
|
910
981
|
linfo = self.check_local_file(tofile, 1, logact)
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
982
|
+
if linfo and linfo['data_size'] == oinfo['data_size']:
|
|
983
|
+
if not chktime: continue
|
|
984
|
+
# an unparsable LastModified leaves date_modified unset; fetch again then
|
|
985
|
+
if 'date_modified' in oinfo and \
|
|
986
|
+
self.cmptime(linfo['date_modified'], linfo['time_modified'],
|
|
987
|
+
oinfo['date_modified'], oinfo['time_modified']) >= 0: continue
|
|
988
|
+
miss.append(key)
|
|
989
|
+
return miss
|
|
990
|
+
|
|
991
|
+
# List the files staged under todir that the object key prefix no longer holds
|
|
992
|
+
# todir - target local directory name
|
|
993
|
+
# prefix - source object key prefix, including the trailing '/'
|
|
994
|
+
# keys - the member keys the object store currently holds
|
|
995
|
+
def local_extra_files(self, todir, prefix, keys):
|
|
996
|
+
"""Return the paths under todir that are not members of an object key prefix.
|
|
997
|
+
|
|
998
|
+
Paths are relative to todir, matching the layout a recursive download creates.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
todir (str): Local directory holding the staged copy.
|
|
1002
|
+
prefix (str): Object key prefix including the trailing '/'.
|
|
1003
|
+
keys (list): The member keys currently under the prefix.
|
|
1004
|
+
|
|
1005
|
+
Returns:
|
|
1006
|
+
list: Relative paths under todir with no matching member key.
|
|
1007
|
+
"""
|
|
1008
|
+
plen = len(prefix)
|
|
1009
|
+
members = set(key[plen:] for key in keys)
|
|
1010
|
+
extra = []
|
|
1011
|
+
for path, dirs, files in os.walk(todir):
|
|
1012
|
+
for fname in files:
|
|
1013
|
+
name = op.relpath(op.join(path, fname), todir)
|
|
1014
|
+
if name not in members: extra.append(name)
|
|
1015
|
+
return extra
|
|
917
1016
|
|
|
918
1017
|
# Copy a remote file to object
|
|
919
1018
|
# tofile - target object file name
|
|
@@ -2376,6 +2475,8 @@ class PgFile(PgUtil, PgSIG):
|
|
|
2376
2475
|
|
|
2377
2476
|
Strips trailing '/'. Uses opt bit 64 to skip the retry. Fetches metadata
|
|
2378
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.
|
|
2379
2480
|
|
|
2380
2481
|
Args:
|
|
2381
2482
|
file (str): Object key to check.
|
|
@@ -2401,7 +2502,11 @@ class PgFile(PgUtil, PgSIG):
|
|
|
2401
2502
|
if buf:
|
|
2402
2503
|
if re.match(r'^\[\]', buf): break
|
|
2403
2504
|
if re.match(r'^\[\{', buf):
|
|
2404
|
-
|
|
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
|
|
2405
2510
|
cnt = len(ary)
|
|
2406
2511
|
isdir = (cnt > 1 or ary[0]['Key'] != file)
|
|
2407
2512
|
# a key prefix has no timestamp of its own; report the newest member's
|
|
@@ -2419,6 +2524,9 @@ class PgFile(PgUtil, PgSIG):
|
|
|
2419
2524
|
for a in ary:
|
|
2420
2525
|
size += int(a['Size'])
|
|
2421
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}
|
|
2422
2530
|
uhash = None
|
|
2423
2531
|
break
|
|
2424
2532
|
if opt&64: return self.FAILURE
|
{rda_python_common-3.0.24 → rda_python_common-3.0.26/src/rda_python_common.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_common
|
|
3
|
-
Version: 3.0.
|
|
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.
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/requires.txt
RENAMED
|
File without changes
|
{rda_python_common-3.0.24 → rda_python_common-3.0.26}/src/rda_python_common.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|