materials-commons-cli 2.3.0b6__tar.gz → 2.3.0b8__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 (105) hide show
  1. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/PKG-INFO +1 -1
  2. materials_commons_cli-2.3.0b8/materials_commons/cli/__init__.py +1 -0
  3. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/async_reconciler.py +26 -21
  4. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/downloader.py +11 -7
  5. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/parser.py +3 -1
  6. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/downloader/file_download_manager.py +3 -0
  7. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/downloader/file_downloader.py +5 -1
  8. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/projects.py +10 -7
  9. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/uploader/file_uploader.py +1 -0
  10. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/ls2.py +22 -17
  11. materials_commons_cli-2.3.0b8/materials_commons/cli/subcommands/mcp.py +86 -0
  12. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/up.py +2 -1
  13. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/walk.py +17 -13
  14. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/PKG-INFO +1 -1
  15. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/SOURCES.txt +1 -0
  16. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/requires.txt +3 -2
  17. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/setup.py +1 -0
  18. materials_commons_cli-2.3.0b6/materials_commons/cli/__init__.py +0 -1
  19. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/LICENSE +0 -0
  20. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/README.md +0 -0
  21. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/LICENSE.txt +0 -0
  22. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/config.py +0 -0
  23. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/filedb.py +0 -0
  24. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/local_project.py +0 -0
  25. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/models.py +0 -0
  26. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/__init__.py +0 -0
  27. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/cloned_project.py +0 -0
  28. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/exceptions.py +0 -0
  29. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/file_functions.py +0 -0
  30. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/functions.py +0 -0
  31. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/globus.py +0 -0
  32. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/list_objects.py +0 -0
  33. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/print_formatter.py +0 -0
  34. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/sqltable.py +0 -0
  35. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/tmp_functions.py +0 -0
  36. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/tree_functions.py +0 -0
  37. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/treedb.py +0 -0
  38. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/old/user_config.py +0 -0
  39. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/reconcile2.py +0 -0
  40. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/reconcile3.py +0 -0
  41. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/requests.py +0 -0
  42. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/run.py +0 -0
  43. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/__init__.py +0 -0
  44. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/__init__.py +0 -0
  45. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/admin_handler_lookup.py +0 -0
  46. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/download_handler_lookup.py +0 -0
  47. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/list_handler_lookup.py +0 -0
  48. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/multi_handler_lookup.py +0 -0
  49. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/protocol.py +0 -0
  50. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/search_find_handler_lookup.py +0 -0
  51. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/command_handlers/upload_handler_lookup.py +0 -0
  52. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/db/__init__.py +0 -0
  53. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/db/db_manager.py +0 -0
  54. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/downloader/__init__.py +0 -0
  55. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/indexer/__init__.py +0 -0
  56. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/indexer/file_index_manager.py +0 -0
  57. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/local_rest_server.py +0 -0
  58. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/ocommand_handlers.py +0 -0
  59. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/project_filedbs.py +0 -0
  60. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/service_container.py +0 -0
  61. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/service_runtime.py +0 -0
  62. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/uploader/__init__.py +0 -0
  63. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/uploader/client_api.py +0 -0
  64. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/uploader/file_upload_manager.py +0 -0
  65. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/server/websocket_server.py +0 -0
  66. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/__init__.py +0 -0
  67. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/clone.py +0 -0
  68. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/config.py +0 -0
  69. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/dataset.py +0 -0
  70. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/down.py +0 -0
  71. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/down2.py +0 -0
  72. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/expt.py +0 -0
  73. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/fetch.py +0 -0
  74. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/globus.py +0 -0
  75. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/init.py +0 -0
  76. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/ls.py +0 -0
  77. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/mkdir.py +0 -0
  78. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/mv.py +0 -0
  79. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/proj.py +0 -0
  80. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/remote.py +0 -0
  81. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/rm.py +0 -0
  82. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/runners/__init__.py +0 -0
  83. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/runners/download_globus_runner.py +0 -0
  84. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/runners/download_v1_runner.py +0 -0
  85. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/scan.py +0 -0
  86. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/server.py +0 -0
  87. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons/cli/subcommands/versions.py +0 -0
  88. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/dependency_links.txt +0 -0
  89. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/entry_points.txt +0 -0
  90. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/not-zip-safe +0 -0
  91. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/materials_commons_cli.egg-info/top_level.txt +0 -0
  92. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/setup.cfg +0 -0
  93. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_api.py +0 -0
  94. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_functions.py +0 -0
  95. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_dataset.py +0 -0
  96. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_down.py +0 -0
  97. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_mkdir.py +0 -0
  98. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_mv.py +0 -0
  99. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_proj.py +0 -0
  100. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_rm.py +0 -0
  101. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_mc_up.py +0 -0
  102. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_treecompare_no_cache.py +0 -0
  103. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_treecompare_with_cache.py +0 -0
  104. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_treedb.py +0 -0
  105. {materials_commons_cli-2.3.0b6 → materials_commons_cli-2.3.0b8}/test/test_user_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: materials_commons_cli
3
- Version: 2.3.0b6
3
+ Version: 2.3.0b8
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
@@ -0,0 +1 @@
1
+ __version__ = '2.3.0b8'
@@ -17,12 +17,14 @@ class AsyncReconciler:
17
17
  db: FileIndexDB,
18
18
  reconcile_mode: ReconcileMode,
19
19
  # recompute_checksum: bool = True,
20
- max_concurrent: int = 10):
20
+ max_concurrent: int = 10,
21
+ batch_size: Optional[int] = None):
21
22
  self.proj = proj
22
23
  self.db = db
23
24
  # self.recompute_checksum = recompute_checksum
24
25
  self.max_concurrent = max_concurrent
25
26
  self.reconcile_mode = reconcile_mode
27
+ self.batch_size = batch_size or max_concurrent
26
28
  self.reconciler = SingleFileReconciler(proj=self.proj, mode=reconcile_mode)
27
29
 
28
30
  async def walk(self, path: str | Path, listdir_fn: ListDirFunc, recursive: bool = False,
@@ -35,36 +37,39 @@ class AsyncReconciler:
35
37
 
36
38
  async for current_path, observations in async_walk(path, recursive=recursive, listdir_fn=listdir_fn,
37
39
  ignore_fn=ignore_fn):
38
- path_states: dict[str, FileState] = {}
39
40
  remote_dir = projects.local_to_remote_project_path(Path(self.proj.local_path), Path(current_path))
40
41
 
41
42
  # Create a map of file_records to name
42
43
  file_records = await self.db.get_files_by_dir(remote_dir.as_posix())
43
44
  file_records_map = {file_record.name: file_record for file_record in file_records}
44
45
 
45
- # Go through all the observations and match to the file records
46
- for obs in observations:
47
- obs.file_record = file_records_map.get(obs.name, None)
48
- obs.remote_path = remote_dir / obs.name
49
- path_states[obs.name] = FileState(observation=obs)
46
+ # Handle a batch of records rather than the entire directory
47
+ for start in range(0, len(observations), self.batch_size):
48
+ batch_observations = observations[start:start + self.batch_size]
49
+ path_states: dict[str, FileState] = {}
50
50
 
51
- results = await asyncio.gather(
52
- *[single_entry_reconcile(state.observation) for state in path_states.values()],
53
- return_exceptions=True)
51
+ # Go through all the observations and match to the file records
52
+ for obs in batch_observations:
53
+ obs.file_record = file_records_map.get(obs.name, None)
54
+ obs.remote_path = remote_dir / obs.name
55
+ path_states[obs.name] = FileState(observation=obs)
54
56
 
55
- # At this point path_entries contains entries in one of 4 states:
56
- # 1. Both remote and local entries exist
57
- # 2. Only remote entry exists
58
- # 3. Only local entry exists
59
- # 4. Lookup failed, and we got an exception
57
+ results = await asyncio.gather(
58
+ *[single_entry_reconcile(state.observation) for state in path_states.values()],
59
+ return_exceptions=True)
60
60
 
61
- for state, result in zip(path_states.values(), results):
62
- if isinstance(result, Exception):
63
- state.exception = result
64
- else:
65
- state.file_decision = result
61
+ # At this point path_entries contains entries in one of 4 states:
62
+ # 1. Both remote and local entries exist
63
+ # 2. Only remote entry exists
64
+ # 3. Only local entry exists
65
+ # 4. Lookup failed, and we got an exception
66
+ for state, result in zip(path_states.values(), results):
67
+ if isinstance(result, Exception):
68
+ state.exception = result
69
+ else:
70
+ state.file_decision = result
66
71
 
67
- yield current_path, path_states
72
+ yield current_path, path_states
68
73
 
69
74
  async def reconcile_file(self, path: str | Path) -> FileState:
70
75
  remote_project_path = projects.local_to_remote_project_path(Path(self.proj.local_path), Path(path))
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import traceback
2
3
  from pathlib import Path
3
4
 
4
5
  from igittigitt import IgnoreParser, igittigitt
@@ -45,13 +46,7 @@ class Downloader:
45
46
  try:
46
47
  for path in paths:
47
48
  p = Path(path)
48
- if p.is_dir():
49
- # Walk and download local dir
50
- await self._download_dir(p, recursive=recursive)
51
- elif p.is_file():
52
- # Download local file
53
- await self._download_file(p)
54
- else:
49
+ if not p.exists():
55
50
  # If we are here, then the path entry is a remote path. We look up the path to
56
51
  # determine the type of download to perform.
57
52
  remote_path = self.proj.to_remote_path(p)
@@ -71,9 +66,16 @@ class Downloader:
71
66
  else:
72
67
  # Remote entry is a file
73
68
  await self._download_file(p, force=force)
69
+ elif p.is_dir():
70
+ # Walk and download local dir
71
+ await self._download_dir(p, recursive=recursive)
72
+ elif p.is_file():
73
+ # Download local file
74
+ await self._download_file(p)
74
75
 
75
76
  except Exception as e:
76
77
  logger.error(f"Unexpected error during download: {e}")
78
+ traceback.print_exc()
77
79
  return False
78
80
 
79
81
  finally:
@@ -114,3 +116,5 @@ class Downloader:
114
116
  print(f"Downloading {entry_name} to {current_path / entry_name}...")
115
117
  transfer_id = await self.container.file_download_manager.download_file(download_request)
116
118
  self.transfer_ids.append(transfer_id)
119
+ else:
120
+ print(f"Skipping download of {entry_name} because {file_state.file_decision.reason}")
@@ -25,6 +25,7 @@ from materials_commons.cli.subcommands.globus import globus_subcommand
25
25
  from materials_commons.cli.subcommands.init import init_subcommand
26
26
  from materials_commons.cli.subcommands.ls import ls_subcommand
27
27
  from materials_commons.cli.subcommands.ls2 import ls2_subcommand
28
+ from materials_commons.cli.subcommands.mcp import mcp_subcommand
28
29
  from materials_commons.cli.subcommands.mkdir import mkdir_subcommand
29
30
  from materials_commons.cli.subcommands.mv import mv_subcommand
30
31
  # from materials_commons.cli.subcommands.proc import ProcSubcommand
@@ -58,7 +59,8 @@ standard_usage = [
58
59
  # {'name': 'ls2', 'desc': 'List directory contents (experimental)', 'subcommand': ls2_subcommand},
59
60
  # {'name': 'down2', 'desc': 'Donwload files (experimental)', 'subcommand': down2_subcommand},
60
61
  {'name': 'config', 'desc': 'Configure `mc`', 'subcommand': config_subcommand},
61
- {'name': 'server', 'desc': 'Run MC Server', 'subcommand': server_subcommand}
62
+ {'name': 'server', 'desc': 'Run MC Server', 'subcommand': server_subcommand},
63
+ {'name': 'mcp', 'desc': 'Run MCP Server', 'subcommand': mcp_subcommand},
62
64
  ]
63
65
  standard_interfaces = {d['name']: d for d in standard_usage}
64
66
 
@@ -25,6 +25,9 @@ class FileDownloadManager:
25
25
 
26
26
  async def start_workers(self) -> None:
27
27
  """Start background workers to process download queue"""
28
+ if self._workers_running:
29
+ logger.info("Download workers already running")
30
+ return
28
31
  self._workers_running = True
29
32
  self._worker_tasks = [
30
33
  asyncio.create_task(self._download_worker(i))
@@ -12,6 +12,7 @@ import requests
12
12
 
13
13
  from materials_commons.cli.old.functions import checksum
14
14
  from materials_commons.cli.requests import DownloadRequest, DBWriteRequest
15
+ from materials_commons.cli.server import projects
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
@@ -32,7 +33,10 @@ class FileDownloader:
32
33
  self.db_write_queue = db_write_queue
33
34
  self.download_request = download_request
34
35
  self.file_id = download_request.observation.remote_entry.remote_file_id
35
- self.file_path = download_request.observation.local_path
36
+ if download_request.observation.local_path is None:
37
+ self.file_path = projects.remote_to_local_project_path(download_request.project.local_path, download_request.observation.remote_path)
38
+ else:
39
+ self.file_path = download_request.observation.local_path
36
40
  self.base_url = download_request.project.remote.base_url
37
41
  self.apitoken = download_request.project.remote.apikey
38
42
  self.project_id = download_request.project.id
@@ -133,13 +133,16 @@ def local_to_remote_project_path(proj_base: Path, full_path: Path) -> Path:
133
133
  Returns:
134
134
  Path: The remote path corresponding to the local path.
135
135
  """
136
- full_path = full_path.resolve()
137
- # remote_path will be the relative path from proj_base to full_path, i.e., it
138
- # won't start with a slash, so we need to add one to get the correct path.
139
- if full_path.as_posix() == proj_base.as_posix():
140
- return Path("/")
141
- remote_path = full_path.relative_to(proj_base)
142
- return Path("/" + remote_path.as_posix())
136
+ try:
137
+ full_path_resolved = full_path.resolve()
138
+ # remote_path will be the relative path from proj_base to full_path, i.e., it
139
+ # won't start with a slash, so we need to add one to get the correct path.
140
+ if full_path_resolved.as_posix() == proj_base.as_posix():
141
+ return Path("/")
142
+ remote_path = full_path_resolved.relative_to(proj_base)
143
+ return Path("/" + remote_path.as_posix())
144
+ except ValueError:
145
+ return full_path
143
146
 
144
147
 
145
148
  def remote_to_local_project_path(proj_base: Path, remote_path: Path) -> Path:
@@ -74,6 +74,7 @@ class FileUploader:
74
74
  if not await self._wait_for_acceptance():
75
75
  if self._already_uploaded:
76
76
  logger.info(f"File {self.file_path} already uploaded")
77
+ print(f"Uploaded {self.file_path}...")
77
78
  return True
78
79
  return False
79
80
 
@@ -90,21 +90,26 @@ async def ls2_subcommand_async(args, working_dir):
90
90
  async_reconciler = AsyncReconciler(db=db, proj=proj, reconcile_mode="status")
91
91
  which_class = LSAction if args.action else LSEntry
92
92
  listdir_fn = make_merged_listdir_func(proj)
93
- for path in args.paths:
94
- lstable = LSTable()
95
- p = Path(path)
96
- if p.is_dir():
97
- async for current_path, path_entries in async_reconciler.walk(path=path, listdir_fn=listdir_fn,
98
- recursive=False, ignore_fn=None):
99
- for entry_name in sorted(path_entries):
100
- entry = path_entries[entry_name]
101
- if entry.exception:
102
- print(f"Error reconciling file: {entry.exception}")
103
- continue
104
- lstable.add_row(which_class.from_file_state(entry))
105
- lstable.print_table()
106
- elif p.is_file():
107
- state = await async_reconciler.reconcile_file(p)
93
+ try:
94
+ for path in args.paths:
108
95
  lstable = LSTable()
109
- lstable.add_row(which_class.from_file_state(state))
110
- lstable.print_table()
96
+ p = Path(path)
97
+ if p.is_dir():
98
+ async for current_path, path_entries in async_reconciler.walk(path=path, listdir_fn=listdir_fn,
99
+ recursive=False, ignore_fn=None):
100
+ for entry_name in sorted(path_entries):
101
+ entry = path_entries[entry_name]
102
+ if entry.exception:
103
+ print(f"Error reconciling file: {entry.exception}")
104
+ continue
105
+ lstable.add_row(which_class.from_file_state(entry))
106
+ lstable.print_table()
107
+ elif p.is_file():
108
+ state = await async_reconciler.reconcile_file(p)
109
+ lstable = LSTable()
110
+ lstable.add_row(which_class.from_file_state(state))
111
+ lstable.print_table()
112
+ except Exception as e:
113
+ pass
114
+ finally:
115
+ await db.close()
@@ -0,0 +1,86 @@
1
+ import argparse
2
+ import functools
3
+ import inspect
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+ from materials_commons.cli.config import Config
7
+ from materials_commons.api.client import Client
8
+
9
+
10
+ mcp = FastMCP(
11
+ name="mcmcp",
12
+ instructions=""
13
+ )
14
+
15
+
16
+ def make_parser():
17
+ parser = argparse.ArgumentParser(
18
+ description="MCP Server for Materials Commons",
19
+ usage="mc mcp",
20
+ )
21
+ parser.add_argument("--port", type=int, default=8000)
22
+ parser.add_argument("--mc-url", type=str, default="https://materialscommons.org/api")
23
+ return parser
24
+
25
+ def mcp_subcommand(argv, working_dir=None):
26
+ parser = make_parser()
27
+ args = parser.parse_args(argv)
28
+ config = Config.load()
29
+ client = config.default_remote.make_client()
30
+ register_mcapi_tools(client)
31
+ mcp.run()
32
+ return args
33
+
34
+ def register_mcapi_tools(client: Client):
35
+ for method_name, method in inspect.getmembers(client, predicate=callable):
36
+ if method_name.startswith("_"):
37
+ continue
38
+ if not is_get_or_list_method(method_name):
39
+ continue
40
+
41
+ doc = inspect.getdoc(method)
42
+ if doc is None:
43
+ continue
44
+
45
+ signature = inspect.signature(method)
46
+
47
+ @functools.wraps(method)
48
+ def tool_wrapper(*args, __method=method, **kwargs):
49
+ result = __method(*args, **kwargs)
50
+ return to_jsonable(result)
51
+
52
+ tool_wrapper.__name__ = method_name
53
+ tool_wrapper.__doc__ = doc
54
+ tool_wrapper.__signature__ = signature
55
+
56
+ mcp.tool(name=method_name)(tool_wrapper)
57
+
58
+ def is_get_or_list_method(method_name: str):
59
+ return method_name.startswith("get_") or method_name.startswith("list_")
60
+
61
+ def to_jsonable(value):
62
+ if value is None:
63
+ return None
64
+
65
+ if isinstance(value, (str, int, float, bool)):
66
+ return value
67
+
68
+ if isinstance(value, list):
69
+ return [to_jsonable(item) for item in value]
70
+
71
+ if isinstance(value, tuple):
72
+ return [to_jsonable(item) for item in value]
73
+
74
+ if isinstance(value, dict):
75
+ return {
76
+ str(key): to_jsonable(item)
77
+ for key, item in value.items()
78
+ }
79
+
80
+ if hasattr(value, "to_dict"):
81
+ return to_jsonable(value.to_dict())
82
+
83
+ if hasattr(value, "__dict__"):
84
+ return to_jsonable(value.__dict__)
85
+
86
+ return str(value)
@@ -208,6 +208,7 @@ async def ws_upload(args, working_dir):
208
208
  file_state = path_entries[entry_name]
209
209
  if file_state.exception:
210
210
  logger.error(f"Error encountered while processing {entry_name}: {file_state.exception}")
211
+ # print(f"Error encountered while processing {entry_name}: {file_state.exception}")
211
212
  continue
212
213
  if file_state.file_decision.action == "upload":
213
214
  upload_request = UploadRequest(observation=file_state.observation,
@@ -219,7 +220,7 @@ async def ws_upload(args, working_dir):
219
220
  file_state = await async_reconciler.reconcile_file(p)
220
221
  if file_state.exception:
221
222
  logger.error(f"Error encountered while processing {p}: {file_state.exception}")
222
- print(f"Error encountered while processing {p}: {file_state.exception}")
223
+ # print(f"Error encountered while processing {p}: {file_state.exception}")
223
224
  continue
224
225
  if file_state.file_decision.action == "upload":
225
226
  upload_request = UploadRequest(observation=file_state.observation,
@@ -118,20 +118,23 @@ async def local_listdir(path: str | Path) -> list[Observation]:
118
118
  def _scan() -> list[Observation]:
119
119
  items: list[Observation] = []
120
120
 
121
- for entry in root.iterdir():
122
- local = path_to_local_file_entry(entry)
123
-
124
- items.append(
125
- Observation(
126
- local_path=entry,
127
- remote_path=None,
128
- local_entry=local,
129
- file_record=None,
130
- remote_entry=None,
121
+ try:
122
+ for entry in root.iterdir():
123
+ local = path_to_local_file_entry(entry)
124
+
125
+ items.append(
126
+ Observation(
127
+ local_path=entry,
128
+ remote_path=None,
129
+ local_entry=local,
130
+ file_record=None,
131
+ remote_entry=None,
132
+ )
131
133
  )
132
- )
133
-
134
- return items
134
+ except:
135
+ pass
136
+ finally:
137
+ return items
135
138
 
136
139
  return await asyncio.to_thread(_scan)
137
140
 
@@ -202,6 +205,7 @@ async def merged_local_remote_listdir(
202
205
  Merge local and remote directory listings, prioritizing local entries.
203
206
  """
204
207
  local_entries = await local_listdir_fn(path)
208
+
205
209
  project_path = projects.local_to_remote_project_path(Path(proj.local_path), path)
206
210
  remote_entries = await remote_listdir_fn(project_path)
207
211
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: materials-commons-cli
3
- Version: 2.3.0b6
3
+ Version: 2.3.0b8
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
@@ -68,6 +68,7 @@ materials_commons/cli/subcommands/globus.py
68
68
  materials_commons/cli/subcommands/init.py
69
69
  materials_commons/cli/subcommands/ls.py
70
70
  materials_commons/cli/subcommands/ls2.py
71
+ materials_commons/cli/subcommands/mcp.py
71
72
  materials_commons/cli/subcommands/mkdir.py
72
73
  materials_commons/cli/subcommands/mv.py
73
74
  materials_commons/cli/subcommands/proj.py
@@ -1,5 +1,5 @@
1
+ websockets
1
2
  aiofiles
2
- aiosqlite
3
3
  globus-cli
4
4
  globus-sdk
5
5
  igittigitt==2.1.0
@@ -10,4 +10,5 @@ requests
10
10
  setuptools
11
11
  sortedcontainers
12
12
  tabulate
13
- websockets
13
+ aiosqlite
14
+ fastmcp
@@ -72,5 +72,6 @@ setup(
72
72
  "sortedcontainers",
73
73
  "tabulate",
74
74
  "aiosqlite",
75
+ "fastmcp",
75
76
  ]
76
77
  )
@@ -1 +0,0 @@
1
- __version__ = '2.3.0b6'