materials-commons-cli 2.0b9__tar.gz → 2.1.1__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 (58) hide show
  1. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/PKG-INFO +9 -8
  2. materials_commons-cli-2.1.1/materials_commons/cli/__init__.py +1 -0
  3. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/functions.py +14 -14
  4. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/globus.py +61 -11
  5. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/parser.py +4 -47
  6. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/dataset.py +8 -3
  7. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/down.py +3 -4
  8. materials_commons-cli-2.1.1/materials_commons/cli/subcommands/fetch.py +92 -0
  9. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/globus.py +7 -2
  10. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/ls.py +4 -1
  11. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/up.py +12 -4
  12. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/tree_functions.py +553 -37
  13. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/treedb.py +48 -19
  14. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/user_config.py +0 -23
  15. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/PKG-INFO +9 -8
  16. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/SOURCES.txt +1 -0
  17. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/entry_points.txt +0 -1
  18. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/requires.txt +1 -0
  19. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/setup.py +1 -0
  20. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_user_config.py +0 -1
  21. materials_commons-cli-2.0b9/materials_commons/cli/__init__.py +0 -1
  22. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/LICENSE +0 -0
  23. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/README.md +0 -0
  24. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/LICENSE.txt +0 -0
  25. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/cloned_project.py +0 -0
  26. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/exceptions.py +0 -0
  27. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/file_functions.py +0 -0
  28. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/list_objects.py +0 -0
  29. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/print_formatter.py +0 -0
  30. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/sqltable.py +0 -0
  31. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/__init__.py +0 -0
  32. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/clone.py +0 -0
  33. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/config.py +0 -0
  34. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/expt.py +0 -0
  35. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/init.py +0 -0
  36. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/mkdir.py +0 -0
  37. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/mv.py +0 -0
  38. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/proj.py +0 -0
  39. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/remote.py +0 -0
  40. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/rm.py +0 -0
  41. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/subcommands/versions.py +0 -0
  42. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons/cli/tmp_functions.py +0 -0
  43. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/dependency_links.txt +0 -0
  44. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/not-zip-safe +0 -0
  45. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/materials_commons_cli.egg-info/top_level.txt +0 -0
  46. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/setup.cfg +0 -0
  47. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_api.py +0 -0
  48. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_functions.py +0 -0
  49. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_dataset.py +0 -0
  50. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_down.py +0 -0
  51. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_mkdir.py +0 -0
  52. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_mv.py +0 -0
  53. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_proj.py +0 -0
  54. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_rm.py +0 -0
  55. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_mc_up.py +0 -0
  56. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_treecompare_no_cache.py +0 -0
  57. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_treecompare_with_cache.py +0 -0
  58. {materials_commons-cli-2.0b9 → materials_commons-cli-2.1.1}/test/test_treedb.py +0 -0
@@ -1,18 +1,12 @@
1
- Metadata-Version: 1.1
1
+ Metadata-Version: 2.1
2
2
  Name: materials_commons-cli
3
- Version: 2.0b9
3
+ Version: 2.1.1
4
4
  Summary: Materials Commons CLI
5
5
  Home-page: https://materials-commons.github.io/materials-commons-cli/html/index.html
6
6
  Author: Materials Commons development team
7
7
  Author-email: materials-commons-authors@umich.edu
8
8
  License: MIT
9
- Description: This package contains the materials_commons.cli module. This module is an interface
10
- to the Materials Commons project. We assume you have used (or are otherwise familiar with) the Materials
11
- Commons web site, https://materialscommons.org/, or a similar site based on the
12
- Materials Commons code (https://github.com/materials-commons/materialscommons), and intend to use these
13
- tools in that context.
14
9
  Keywords: materials science mc materials-commons prisms
15
- Platform: UNKNOWN
16
10
  Classifier: Development Status :: 4 - Beta
17
11
  Classifier: Intended Audience :: Developers
18
12
  Classifier: Intended Audience :: Science/Research
@@ -20,3 +14,10 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
20
14
  Classifier: License :: OSI Approved :: MIT License
21
15
  Classifier: Programming Language :: Python :: 3
22
16
  Classifier: Programming Language :: Python :: 3.8
17
+ License-File: LICENSE
18
+
19
+ This package contains the materials_commons.cli module. This module is an interface
20
+ to the Materials Commons project. We assume you have used (or are otherwise familiar with) the Materials
21
+ Commons web site, https://materialscommons.org/, or a similar site based on the
22
+ Materials Commons code (https://github.com/materials-commons/materialscommons), and intend to use these
23
+ tools in that context.
@@ -0,0 +1 @@
1
+ __version__ = '2.1.1'
@@ -60,21 +60,21 @@ def epoch_time(time_value):
60
60
  """Attempts to convert various time representations into s since the epoch
61
61
 
62
62
  Args:
63
- time_value: A representation of time.
63
+ time_value: A representation of time, expects UTC time.
64
64
 
65
65
  Returns:
66
- An integer s since the epoch. Uses the following:
66
+ An float s since the epoch. Uses the following:
67
67
 
68
68
  +-------------------+------------------------------------------------------------+
69
69
  | If this type | Then do this conversion |
70
70
  +-------------------+------------------------------------------------------------+
71
- | str | time.mktime(dateutil.parser.parse(time_value).timetuple()) |
71
+ | str | dateutil.parser.parse(time_value).timestamp() |
72
72
  +-------------------+------------------------------------------------------------+
73
73
  | float, int | time_value |
74
74
  +-------------------+------------------------------------------------------------+
75
- | datetime.datetime | time.mktime(time_value.timetuple()) |
75
+ | datetime.datetime | time_value.replace(tzinfo=datetime.timezone.utc).timestamp() |
76
76
  +-------------------+------------------------------------------------------------+
77
- | dict | time_value['epoch_time'] |
77
+ | dict | float(time_value['epoch_time']) |
78
78
  +-------------------+------------------------------------------------------------+
79
79
  | None | None |
80
80
  +-------------------+------------------------------------------------------------+
@@ -82,13 +82,13 @@ def epoch_time(time_value):
82
82
  +-------------------+------------------------------------------------------------+
83
83
  """
84
84
  if isinstance(time_value, str): # expect ISO 8601 str
85
- return time.mktime(dateutil.parser.parse(time_value).timetuple())
85
+ return dateutil.parser.parse(time_value).timestamp()
86
86
  elif isinstance(time_value, (float, int)):
87
- return time_value
87
+ return float(time_value)
88
88
  elif isinstance(time_value, datetime.datetime):
89
- return time.mktime(time_value.timetuple())
89
+ return time_value.replace(tzinfo=datetime.timezone.utc).timestamp()
90
90
  elif isinstance(time_value, dict) and ('epoch_time' in time_value):
91
- return time_value['epoch_time']
91
+ return float(time_value['epoch_time'])
92
92
  elif time_value is None:
93
93
  return None
94
94
  else:
@@ -98,12 +98,12 @@ def format_time(time_value, fmt="%Y %b %d %H:%M:%S"):
98
98
  """Attempts to put various time representations into specified format for printing
99
99
 
100
100
  Args:
101
- time_value: A representation of time.
101
+ time_value: A representation of time, expects UTC time.
102
102
 
103
103
  fmt (str): Format to use for the return value.
104
104
 
105
105
  Returns:
106
- A string representation using the specified format. Uses the following:
106
+ A string representation, in local time, using the specified format. Uses the following:
107
107
 
108
108
  +-------------------+-------------------------------------------------+
109
109
  | If this type | Then do this conversion |
@@ -112,7 +112,7 @@ def format_time(time_value, fmt="%Y %b %d %H:%M:%S"):
112
112
  +-------------------+-------------------------------------------------+
113
113
  | float, int | time.strftime(fmt, time.localtime(time_value)) |
114
114
  +-------------------+-------------------------------------------------+
115
- | datetime.datetime | time.strftime(fmt, time.localtime(time_value)) |
115
+ | datetime.datetime | time_value.strftime(fmt) |
116
116
  +-------------------+-------------------------------------------------+
117
117
  | dict | format_time(time_value['epoch_time']) |
118
118
  +-------------------+-------------------------------------------------+
@@ -122,11 +122,11 @@ def format_time(time_value, fmt="%Y %b %d %H:%M:%S"):
122
122
  +-------------------+-------------------------------------------------+
123
123
  """
124
124
  if isinstance(time_value, str): # expect ISO 8601 str
125
- return dateutil.parser.parse(time_value).strftime(fmt)
125
+ return dateutil.parser.parse(time_value).astimezone().strftime(fmt)
126
126
  elif isinstance(time_value, (float, int)):
127
127
  return time.strftime(fmt, time.localtime(time_value))
128
128
  elif isinstance(time_value, datetime.datetime):
129
- return time_value.strftime(fmt)
129
+ return time_value.astimezone().strftime(fmt)
130
130
  elif isinstance(time_value, dict) and ('epoch_time' in time_value):
131
131
  return format_time(time_value['epoch_time'])
132
132
  elif time_value is None:
@@ -1,4 +1,5 @@
1
1
  import globus_sdk
2
+ from globus_sdk.scopes import TransferScopes
2
3
  import os
3
4
 
4
5
  import materials_commons.api as mcapi
@@ -9,8 +10,16 @@ from materials_commons.cli.user_config import Config
9
10
 
10
11
  CLIENT_ID = '1e4aacbc-8c10-4812-a54a-8434d2030a41'
11
12
 
13
+ def check_for_consent_required(client, target):
14
+ try:
15
+ client.operation_ls(target, path="/")
16
+ except globus_sdk.TransferAPIError as err:
17
+ if err.info.consent_required:
18
+ return err.info.consent_required.required_scopes
19
+ return None
20
+
12
21
 
13
- def get_transfer_rt_or_login():
22
+ def get_transfer_rt_or_login(scopes=TransferScopes.all, reauth=False):
14
23
  """Get the Globus transfer refresh token, prompting for login if necessary
15
24
 
16
25
  If not yet configured, will prompt user to login and enter a code used to obtain the refresh
@@ -25,13 +34,14 @@ def get_transfer_rt_or_login():
25
34
  config = Config()
26
35
 
27
36
  # return existing transfer_rt
28
- if config.globus.transfer_rt:
29
- return config.globus.transfer_rt
37
+ if not reauth:
38
+ if config.globus.transfer_rt:
39
+ return config.globus.transfer_rt
30
40
 
31
41
  # authenticate
32
42
 
33
43
  client = globus_sdk.NativeAppAuthClient(CLIENT_ID)
34
- client.oauth2_start_flow(refresh_tokens=True)
44
+ client.oauth2_start_flow(refresh_tokens=True, requested_scopes=scopes)
35
45
 
36
46
  authorize_url = client.oauth2_get_authorize_url()
37
47
  try:
@@ -202,31 +212,50 @@ class GlobusOperations(object):
202
212
  return
203
213
 
204
214
 
205
- def upload_v0(self, proj, paths, upload, working_dir, recursive=False,
215
+ def upload_v0(self, proj, paths, upload, working_dir, recursive=False, no_compare=False,
206
216
  label=None, localtree=None, remotetree=None):
207
217
  """Upload files and directories to project
208
218
 
209
219
  Arguments:
210
220
  proj: Project
211
- paths: list of str, Materials Commons paths (include project directory)
221
+ paths (List of str): Materials Commons paths (include project directory) to upload
212
222
  upload: mcapi.GlobusUpload, Globus upload request
213
223
  working_dir (str): Current working directory, used for finding
214
224
  relative paths and printing messages.
215
225
  recursive: bool, If True, upload directories recursively
226
+ no_compare (bool): If False (default), compare checksum to skip overwriting equivalent
227
+ files. If True, transfer without checking.
216
228
  label: str, Globus transfer label to make finding tasks simpler
217
229
 
218
230
  Returns:
219
231
  None or task_id: str, transfer task id. Returns nothing to transfer.
220
232
  """
221
- paths = treefuncs.make_mcpaths_for_upload(proj.local_path, paths)
222
-
223
233
  if not len(paths):
224
234
  return None
225
235
 
226
236
  files_data, dirs_data, child_data, non_existing = treefuncs.treecompare(proj, paths, checksum=True, localtree=localtree, remotetree=remotetree)
227
237
 
228
238
  # https://globus-sdk-python.readthedocs.io/en/stable/clients/transfer/#globus_sdk.TransferData
229
- tdata = globus_sdk.TransferData(self.tc, self.local_endpoint_id, upload.globus_endpoint_id, label=label)
239
+ sync_level = "checksum"
240
+ if no_compare is True:
241
+ sync_level = None
242
+
243
+ consent_required_scopes = []
244
+ consent_required = check_for_consent_required(self.tc, self.local_endpoint_id)
245
+ if consent_required is not None:
246
+ consent_required_scopes.extend(consent_required)
247
+
248
+ consent_required = check_for_consent_required(self.tc, upload.globus_endpoint_id)
249
+ if consent_required is not None:
250
+ consent_required_scopes.extend(consent_required)
251
+
252
+ if consent_required_scopes:
253
+ print("One or more of the endpoints requires consent in order to be used.\n"
254
+ "You must login a second time to grant consents.\n\n")
255
+ tr = get_transfer_rt_or_login(scopes=consent_required_scopes, reauth=True)
256
+ self.tc = make_transfer_client(tr)
257
+
258
+ tdata = globus_sdk.TransferData(self.tc, self.local_endpoint_id, upload.globus_endpoint_id, label=label, sync_level=sync_level)
230
259
 
231
260
  # add items
232
261
  n_items = 0
@@ -270,7 +299,7 @@ class GlobusOperations(object):
270
299
 
271
300
  return task_id
272
301
 
273
- def download_v0(self, proj, paths, download, working_dir, recursive=False,
302
+ def download_v0(self, proj, paths, download, working_dir, recursive=False, no_compare=False,
274
303
  label=None, localtree=None, remotetree=None, force=False):
275
304
  """Download files and directories from project
276
305
 
@@ -282,6 +311,8 @@ class GlobusOperations(object):
282
311
  working_dir (str): Current working directory, used for finding
283
312
  relative paths and printing messages.
284
313
  recursive: bool, If True, download directories recursively
314
+ no_compare (bool): If False (default), compare checksum to skip overwriting equivalent
315
+ files. If True, transfer without checking.
285
316
  label: str, Globus transfer label to make finding tasks simpler
286
317
  force: bool, If True force download even if local file exists
287
318
 
@@ -295,7 +326,26 @@ class GlobusOperations(object):
295
326
  files_data, dirs_data, child_data, non_existing = treefuncs.treecompare(proj, paths, checksum=True, localtree=localtree, remotetree=remotetree)
296
327
 
297
328
  # https://globus-sdk-python.readthedocs.io/en/stable/clients/transfer/#globus_sdk.TransferData
298
- tdata = globus_sdk.TransferData(self.tc, download.globus_endpoint_id, self.local_endpoint_id, label=label)
329
+ sync_level = "checksum"
330
+ if no_compare is True:
331
+ sync_level = None
332
+
333
+ consent_required_scopes = []
334
+ consent_required = check_for_consent_required(self.tc, self.local_endpoint_id)
335
+ if consent_required is not None:
336
+ consent_required_scopes.extend(consent_required)
337
+
338
+ consent_required = check_for_consent_required(self.tc, download.globus_endpoint_id)
339
+ if consent_required is not None:
340
+ consent_required_scopes.extend(consent_required)
341
+
342
+ if consent_required_scopes:
343
+ print("One or more of the endpoints requires consent in order to be used.\n"
344
+ "You must login a second time to grant consents.\n\n")
345
+ tr = get_transfer_rt_or_login(scopes=consent_required_scopes, reauth=True)
346
+ self.tc = make_transfer_client(tr)
347
+
348
+ tdata = globus_sdk.TransferData(self.tc, download.globus_endpoint_id, self.local_endpoint_id, label=label, sync_level=sync_level)
299
349
 
300
350
  # add items
301
351
  n_items = 0
@@ -1,5 +1,4 @@
1
1
  import argparse
2
- import imp
3
2
  import os
4
3
  import sys
5
4
  from io import StringIO
@@ -9,10 +8,9 @@ import materials_commons.api as mcapi
9
8
  from materials_commons.cli.subcommands.clone import clone_subcommand
10
9
  from materials_commons.cli.subcommands.config import config_subcommand
11
10
  from materials_commons.cli.subcommands.dataset import DatasetSubcommand
12
- # from materials_commons.cli.subcommands.diff import diff_subcommand
13
11
  from materials_commons.cli.subcommands.down import down_subcommand
14
12
  from materials_commons.cli.subcommands.expt import ExptSubcommand
15
- # from materials_commons.cli.subcommands.fetch import fetch_subcommand
13
+ from materials_commons.cli.subcommands.fetch import fetch_subcommand
16
14
  from materials_commons.cli.subcommands.init import init_subcommand
17
15
  from materials_commons.cli.subcommands.ls import ls_subcommand
18
16
  from materials_commons.cli.subcommands.mkdir import mkdir_subcommand
@@ -42,24 +40,18 @@ standard_usage = [
42
40
  {'name': 'mkdir', 'desc': 'Make directories', 'subcommand': mkdir_subcommand},
43
41
  {'name': 'rm', 'desc': 'Remove files and directories', 'subcommand': rm_subcommand},
44
42
  {'name': 'mv', 'desc': 'Move files', 'subcommand': mv_subcommand},
45
- # {'name': 'diff', 'desc': 'Compare local and remote files', 'subcommand': diff_subcommand},
46
- # {'name': 'fetch', 'desc': 'Remote data fetching and configuration', 'subcommand': fetch_subcommand},
43
+ {'name': 'fetch', 'desc': 'Remote data fetching and configuration', 'subcommand': fetch_subcommand},
47
44
  {'name': 'up', 'desc': 'Upload files', 'subcommand': up_subcommand},
48
45
  {'name': 'down', 'desc': 'Download files', 'subcommand': down_subcommand},
49
46
  {'name': 'globus', 'desc': 'Manage Globus uploads and downloads', 'subcommand': globus_subcommand},
50
47
  {'name': 'versions', 'desc': 'List file versions', 'subcommand': versions_subcommand},
51
- # {'name': 'templates', 'desc': 'List process templates', 'subcommand': TemplatesSubcommand()},
52
48
  # {'name': 'proc', 'desc': 'List processes', 'subcommand': ProcSubcommand()},
53
49
  # {'name': 'samp', 'desc': 'List samples', 'subcommand': SampSubcommand()},
54
50
  {'name': 'config', 'desc': 'Configure `mc`', 'subcommand': config_subcommand}
55
51
  ]
56
52
  standard_interfaces = {d['name']: d for d in standard_usage}
57
53
 
58
- developer_usage = [
59
- # {'name': 'actions', 'desc': 'List REST API actions', 'subcommand': ActionsSubcommand()}
60
- ]
61
-
62
- def make_parser(custom_interfaces={}, developer_interfaces={}):
54
+ def make_parser():
63
55
 
64
56
  usage_help = StringIO()
65
57
  usage_help.write("mc <command> [<args>]\n\n")
@@ -68,18 +60,6 @@ def make_parser(custom_interfaces={}, developer_interfaces={}):
68
60
  for name, interface in standard_interfaces.items():
69
61
  usage_help.write(" {:10} {:40}\n".format(name, interface['desc']))
70
62
 
71
- # read custom interfaces from config file
72
- if len(custom_interfaces):
73
- usage_help.write("\nSpecialized commands are:\n")
74
- for name, interface in custom_interfaces.items():
75
- usage_help.write(" {:10} {:40}\n".format(name, interface['desc']))
76
-
77
- # hide from most users
78
- if len(developer_interfaces):
79
- usage_help.write("\nDeveloper commands are:\n")
80
- for name, interface in developer_interfaces.items():
81
- usage_help.write(" {:10} {:40}\n".format(name, interface['desc']))
82
-
83
63
  parser = argparse.ArgumentParser(
84
64
  description='Materials Commons command line interface',
85
65
  usage=usage_help.getvalue())
@@ -100,13 +80,7 @@ def main(argv=None, working_dir=None):
100
80
  mcapi.Client.set_debug_on()
101
81
  pass
102
82
 
103
- custom_interfaces = {d['name']: d for d in config.interfaces}
104
-
105
- developer_interfaces = {}
106
- if config.developer_mode:
107
- developer_interfaces = {d['name']: d for d in developer_usage}
108
-
109
- parser = make_parser(custom_interfaces, developer_interfaces)
83
+ parser = make_parser()
110
84
 
111
85
  if len(argv) < 2:
112
86
  parser.print_help()
@@ -120,23 +94,6 @@ def main(argv=None, working_dir=None):
120
94
  result = standard_interfaces[args.command]['subcommand'](argv[2:], working_dir)
121
95
  return result
122
96
 
123
- elif args.command in custom_interfaces:
124
- # load module and run command
125
- modulename = custom_interfaces[args.command]['module']
126
- subcommandname = custom_interfaces[args.command]['subcommand']
127
- f, filename, description = imp.find_module(modulename)
128
- try:
129
- module = imp.load_module(modulename, f, filename, description)
130
- result = getattr(module, subcommandname)(argv[2:], working_dir)
131
- finally:
132
- if f:
133
- f.close()
134
- return result
135
-
136
- elif args.command in developer_interfaces:
137
- result = developer_interfaces[args.command]['subcommand'](argv[2:], working_dir)
138
- return result
139
-
140
97
  else:
141
98
  print('Unrecognized command')
142
99
  parser.print_help()
@@ -147,7 +147,7 @@ class DatasetSubcommand(ListObjects):
147
147
  def print_details(self, obj, args, out=sys.stdout):
148
148
  # TODO: fix this
149
149
  out.write("** WARNING: `mc dataset --details` is under development, some dataset attributes (for example: tags) may appear as 'null' even if they do exist. **\n")
150
- if args.all:
150
+ if args.all or not clifuncs.project_exists(self.working_dir):
151
151
  client = self.get_remote(args)
152
152
  if args.file_selection:
153
153
  out.write("** NOTE: --file-selection: Not available for public datasets **\n")
@@ -191,13 +191,18 @@ class DatasetSubcommand(ListObjects):
191
191
 
192
192
  .. note:: The downloaded dataset is named dataset.<dataset_uuid>.zip
193
193
  """
194
- proj = clifuncs.make_local_project(self.working_dir)
194
+
195
+ if args.all or not clifuncs.project_exists(self.working_dir):
196
+ remote = self.get_remote(args)
197
+ else:
198
+ proj = clifuncs.make_local_project(self.working_dir)
199
+ remote = proj.remote
195
200
  for obj in objects:
196
201
  self.print_details(obj, args, out=out)
197
202
  out.write("Downloading...\n")
198
203
  dataset_id = obj.id
199
204
  to = "dataset." + obj.uuid + ".zip"
200
- proj.remote.download_published_dataset_zipfile(dataset_id, to)
205
+ remote.download_published_dataset_zipfile(dataset_id, to)
201
206
  out.write("DONE\n\n")
202
207
  return
203
208
 
@@ -300,7 +300,7 @@ def down_subcommand(argv, working_dir):
300
300
  + " not ready for downloading. Materials Commons is still preparing the project"
301
301
  + " files for download. For large projects this may take some time.")
302
302
  print("Use `mc globus download` to check when it is ready and try again.")
303
- raise cliexcept.MCCLIException("Invalid download request")
303
+ return
304
304
 
305
305
  print("Download is ready.")
306
306
 
@@ -310,9 +310,8 @@ def down_subcommand(argv, working_dir):
310
310
 
311
311
  globus_ops = cliglobus.GlobusOperations()
312
312
  task_id = globus_ops.download_v0(proj, paths, download, working_dir,
313
- recursive=args.recursive, label=label,
314
- localtree=localtree,
315
- remotetree=remotetree,
313
+ recursive=args.recursive, label=label, localtree=localtree,
314
+ remotetree=remotetree, no_compare=args.no_compare,
316
315
  force=args.force)
317
316
 
318
317
  if task_id:
@@ -0,0 +1,92 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+ import time
5
+
6
+ import materials_commons.api as mcapi
7
+ import materials_commons.cli.functions as clifuncs
8
+ import materials_commons.cli.tree_functions as treefuncs
9
+ import materials_commons.cli.file_functions as filefuncs
10
+ from materials_commons.cli.treedb import LocalTree, RemoteTree
11
+
12
+
13
+ def print_fetch_status(pconfig):
14
+ if pconfig.remote_updatetime:
15
+ print("Fetch lock set at:", clifuncs.format_time(pconfig.remote_updatetime))
16
+ print("Project data will at least reflect remote changes that happened before this time.")
17
+ print("**Changes that happen after this time may not be reflected in CLI output**.")
18
+ print("Do `mc fetch --unlock` to turn off the fetch lock.")
19
+ else:
20
+ print("Fetch lock off")
21
+ print("Project data will always be fetched from remote.")
22
+
23
+ def make_parser():
24
+ desc = "By default, remote data is fetched for every request. To be more efficient, some remote data can be fetched and cached using `mc fetch --lock`. When 'locked', remote data is recovered from a local cache if it has been updated since the lock was put in place. Any data fetched before the lock was put in place is still updated as necessary. To the extent possible, the cache will be updated when the remote is modified via the `mc` command line. While the lock is in place, use `mc fetch <path>` to force the update of data for particular files or directories. To reset the lock time, use `mc fetch --lock` again. To stop caching, use `mc fetch --unlock`."
25
+
26
+ parser = argparse.ArgumentParser(
27
+ description=desc,
28
+ prog='mc fetch')
29
+ parser.add_argument('paths', nargs='*', default=[], help='Files or directories')
30
+ parser.add_argument('-r', '--recursive', action="store_true", default=False, help='Fetch data recursively for specified paths.')
31
+ parser.add_argument('--verbose', action="store_true", default=False, help='Print verbosely.')
32
+ parser.add_argument('--no-children', action="store_true", default=False, help='Do not update data for children of directories.')
33
+ parser.add_argument('--lock', action="store_true", default=False, help='Only fetch data to replace records older than now.')
34
+ parser.add_argument('--unlock', action="store_true", default=False, help='Always fetch data.')
35
+ parser.add_argument('--status', action="store_true", default=False, help='Display fetch lock status.')
36
+ return parser
37
+
38
+ def fetch_subcommand(argv, working_dir):
39
+ """
40
+ Fetch remote data
41
+
42
+ mc fetch [--recursive] [<path>...]
43
+ mc fetch --lock
44
+ mc fetch --unlock
45
+ mc fetch --status
46
+
47
+ """
48
+ parser = make_parser()
49
+ args = parser.parse_args(argv)
50
+
51
+ proj = clifuncs.make_local_project(working_dir)
52
+ pconfig = clifuncs.read_project_config(proj.local_path)
53
+
54
+ if args.lock:
55
+ pconfig.remote_updatetime = time.time()
56
+ pconfig.save()
57
+ print_fetch_status(pconfig)
58
+
59
+ elif args.unlock:
60
+ pconfig.remote_updatetime = None
61
+ pconfig.save()
62
+ print_fetch_status(pconfig)
63
+
64
+ elif args.status:
65
+ print_fetch_status(pconfig)
66
+
67
+ else:
68
+ if not args.paths:
69
+ print("Nothing to fetch")
70
+ return
71
+
72
+ remotetree = RemoteTree(proj, time.time())
73
+ refpath = os.path.dirname(proj.local_path)
74
+
75
+ get_children = True
76
+ if args.no_children:
77
+ get_children = False
78
+
79
+ mcpaths = treefuncs.clipaths_to_mcpaths(proj.local_path, args.paths,
80
+ working_dir)
81
+
82
+ remotetree.connect()
83
+ for mcpath in mcpaths:
84
+ print("fetch:", mcpath)
85
+ remotetree.update(
86
+ mcpath,
87
+ get_children=get_children,
88
+ recurs=args.recursive,
89
+ verbose=args.verbose)
90
+ remotetree.close()
91
+
92
+ return
@@ -59,9 +59,13 @@ class GlobusUploadTaskSubcommand(ListObjects):
59
59
  upload.project_id = upload._data["project_id"]
60
60
  status_code = upload._data["status"]
61
61
  if status_code == 0:
62
+ status_message = "Done" # user should not see this
63
+ elif status_code == 1:
62
64
  status_message = "Finishing" # processing uploaded files into project
63
65
  elif status_code == 2:
64
- status_message = "Ready" # user may upload
66
+ status_message = "Ready" # user may upload
67
+ elif status_code == 3:
68
+ status_message = "Not started" # user should not see this
65
69
  else:
66
70
  status_message = "Error - Unknown status code: " + str(status_code)
67
71
  upload.status_code = status_code
@@ -284,7 +288,8 @@ class GlobusDownloadTaskSubcommand(ListObjects):
284
288
  headers=['', 'project_name', 'project_id', 'type', 'name', 'id', 'created', 'status'],
285
289
  deletable=True,
286
290
  creatable=True,
287
- custom_selection_actions=['goto'],
291
+ custom_actions=['unset'],
292
+ custom_selection_actions=['goto', 'set'],
288
293
  request_confirmation_actions={
289
294
  'goto': 'You want to goto these downloads in a web browser?'
290
295
  }
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import copy
3
+ import json
3
4
  import os
4
5
  import sys
5
6
  import time
@@ -119,9 +120,11 @@ def _ls_print(proj, data, refpath=None, printjson=False, checksum=False, checkds
119
120
  headers += ['selected', 'selected_by']
120
121
 
121
122
  if printjson:
123
+ output = []
122
124
  for path, record in data.items():
123
125
  if record['r_obj']:
124
- print(record['r_obj'].input_data)
126
+ output.append(record['r_obj']._data)
127
+ print(json.dumps(output))
125
128
  else:
126
129
  path_data = _format_path_data(proj, data, columns, refpath=refpath, checksum=checksum)
127
130
  if len(path_data):
@@ -74,8 +74,15 @@ def up_subcommand(argv, working_dir):
74
74
 
75
75
  if args.globus:
76
76
 
77
- mcpaths = treefuncs.clipaths_to_mcpaths(proj.local_path, args.paths,
78
- working_dir)
77
+ # convert input paths (absolute or relative to working_dir) to local_abspath
78
+ local_abspaths = treefuncs.clipaths_to_local_abspaths(
79
+ proj.local_path, args.paths, working_dir)
80
+
81
+ # filter, skipping .mc, those specified by .mcignore
82
+ local_abspaths = treefuncs.filter_local_abspaths(
83
+ proj.local_path, local_abspaths, working_dir)
84
+
85
+ mcpaths = treefuncs.clipaths_to_mcpaths(proj.local_path, local_abspaths, working_dir)
79
86
 
80
87
  all_uploads = {upload.id:upload for upload in proj.remote.get_all_globus_upload_requests(proj.id)}
81
88
 
@@ -104,7 +111,8 @@ def up_subcommand(argv, working_dir):
104
111
 
105
112
  globus_ops = cliglobus.GlobusOperations()
106
113
  task_id = globus_ops.upload_v0(proj, mcpaths, upload, working_dir,
107
- recursive=args.recursive, label=label)
114
+ recursive=args.recursive, no_compare=args.no_compare,
115
+ label=label)
108
116
 
109
117
  if task_id:
110
118
  print("Globus transfer task initiated.")
@@ -119,7 +127,7 @@ def up_subcommand(argv, working_dir):
119
127
  if not args.no_compare:
120
128
  localtree = LocalTree(proj.local_path)
121
129
 
122
- treefuncs.standard_upload(proj, args.paths, working_dir,
130
+ treefuncs.standard_upload_v2(proj, args.paths, working_dir,
123
131
  recursive=args.recursive, limit=args.limit[0],
124
132
  no_compare=args.no_compare,
125
133
  upload_as=upload_as, localtree=localtree,