materials-commons-cli 2.3.0b11__tar.gz → 2.3.0b12__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 (108) hide show
  1. {materials_commons_cli-2.3.0b11/materials_commons_cli.egg-info → materials_commons_cli-2.3.0b12}/PKG-INFO +1 -2
  2. materials_commons_cli-2.3.0b12/materials_commons/cli/__init__.py +1 -0
  3. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/parser.py +3 -8
  4. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/uploader/file_uploader.py +15 -0
  5. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/websocket_server.py +1 -1
  6. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/up.py +6 -1
  7. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12/materials_commons_cli.egg-info}/PKG-INFO +1 -2
  8. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/SOURCES.txt +0 -1
  9. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/requires.txt +0 -1
  10. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/setup.py +0 -1
  11. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/tests/test_file_uploader.py +272 -0
  12. materials_commons_cli-2.3.0b11/materials_commons/cli/__init__.py +0 -1
  13. materials_commons_cli-2.3.0b11/materials_commons/cli/subcommands/mcp.py +0 -86
  14. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/LICENSE +0 -0
  15. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/README.md +0 -0
  16. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/LICENSE.txt +0 -0
  17. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/async_reconciler.py +0 -0
  18. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/config.py +0 -0
  19. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/downloader.py +0 -0
  20. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/filedb.py +0 -0
  21. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/local_project.py +0 -0
  22. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/models.py +0 -0
  23. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/__init__.py +0 -0
  24. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/cloned_project.py +0 -0
  25. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/exceptions.py +0 -0
  26. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/file_functions.py +0 -0
  27. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/functions.py +0 -0
  28. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/globus.py +0 -0
  29. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/list_objects.py +0 -0
  30. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/print_formatter.py +0 -0
  31. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/sqltable.py +0 -0
  32. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/tmp_functions.py +0 -0
  33. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/tree_functions.py +0 -0
  34. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/treedb.py +0 -0
  35. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/old/user_config.py +0 -0
  36. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/reconcile2.py +0 -0
  37. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/reconcile3.py +0 -0
  38. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/requests.py +0 -0
  39. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/run.py +0 -0
  40. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/__init__.py +0 -0
  41. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/__init__.py +0 -0
  42. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/admin_handler_lookup.py +0 -0
  43. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/download_handler_lookup.py +0 -0
  44. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/list_handler_lookup.py +0 -0
  45. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/multi_handler_lookup.py +0 -0
  46. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/protocol.py +0 -0
  47. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/search_find_handler_lookup.py +0 -0
  48. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/command_handlers/upload_handler_lookup.py +0 -0
  49. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/db/__init__.py +0 -0
  50. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/db/db_manager.py +0 -0
  51. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/downloader/__init__.py +0 -0
  52. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/downloader/file_download_manager.py +0 -0
  53. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/downloader/file_downloader.py +0 -0
  54. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/indexer/__init__.py +0 -0
  55. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/indexer/file_index_manager.py +0 -0
  56. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/local_rest_server.py +0 -0
  57. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/ocommand_handlers.py +0 -0
  58. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/project_filedbs.py +0 -0
  59. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/projects.py +0 -0
  60. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/service_container.py +0 -0
  61. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/service_runtime.py +0 -0
  62. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/uploader/__init__.py +0 -0
  63. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/uploader/client_api.py +0 -0
  64. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/server/uploader/file_upload_manager.py +0 -0
  65. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/__init__.py +0 -0
  66. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/clone.py +0 -0
  67. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/config.py +0 -0
  68. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/dataset.py +0 -0
  69. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/down.py +0 -0
  70. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/down2.py +0 -0
  71. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/expt.py +0 -0
  72. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/fetch.py +0 -0
  73. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/globus.py +0 -0
  74. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/init.py +0 -0
  75. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/ls.py +0 -0
  76. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/ls2.py +0 -0
  77. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/mkdir.py +0 -0
  78. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/mv.py +0 -0
  79. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/proj.py +0 -0
  80. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/remote.py +0 -0
  81. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/rm.py +0 -0
  82. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/runners/__init__.py +0 -0
  83. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/runners/download_globus_runner.py +0 -0
  84. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/runners/download_v1_runner.py +0 -0
  85. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/scan.py +0 -0
  86. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/server.py +0 -0
  87. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/subcommands/versions.py +0 -0
  88. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/terminal_progress.py +0 -0
  89. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons/cli/walk.py +0 -0
  90. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/dependency_links.txt +0 -0
  91. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/entry_points.txt +0 -0
  92. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/not-zip-safe +0 -0
  93. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/materials_commons_cli.egg-info/top_level.txt +0 -0
  94. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/setup.cfg +0 -0
  95. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_api.py +0 -0
  96. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_functions.py +0 -0
  97. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_dataset.py +0 -0
  98. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_down.py +0 -0
  99. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_mkdir.py +0 -0
  100. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_mv.py +0 -0
  101. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_proj.py +0 -0
  102. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_rm.py +0 -0
  103. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_mc_up.py +0 -0
  104. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_treecompare_no_cache.py +0 -0
  105. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_treecompare_with_cache.py +0 -0
  106. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_treedb.py +0 -0
  107. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/test/test_user_config.py +0 -0
  108. {materials_commons_cli-2.3.0b11 → materials_commons_cli-2.3.0b12}/tests/test_reconcile3.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: materials_commons_cli
3
- Version: 2.3.0b11
3
+ Version: 2.3.0b12
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
@@ -27,7 +27,6 @@ Requires-Dist: requests~=2.28
27
27
  Requires-Dist: sortedcontainers~=2.4
28
28
  Requires-Dist: tabulate~=0.9
29
29
  Requires-Dist: aiosqlite~=0.22
30
- Requires-Dist: fastmcp~=3.4
31
30
  Dynamic: author
32
31
  Dynamic: author-email
33
32
  Dynamic: classifier
@@ -0,0 +1 @@
1
+ __version__ = '2.3.0b12'
@@ -1,16 +1,15 @@
1
1
  import argparse
2
2
  import json
3
3
  import logging
4
+ import materials_commons.api as mcapi
4
5
  import os
5
6
  import sys
6
7
  from datetime import datetime, timedelta
7
8
  from importlib.metadata import PackageNotFoundError, version as pkg_version
8
9
  from io import StringIO
9
10
 
10
- import materials_commons.api as mcapi
11
- import requests
12
-
13
11
  import materials_commons.cli.old.functions as clifuncs
12
+ import requests
14
13
  from materials_commons.cli.old.exceptions import MCCLIException, MissingRemoteException, \
15
14
  MultipleRemoteException, NoDefaultRemoteException
16
15
  from materials_commons.cli.old.user_config import Config
@@ -18,15 +17,12 @@ from materials_commons.cli.subcommands.clone import clone_subcommand
18
17
  from materials_commons.cli.subcommands.config import config_subcommand
19
18
  from materials_commons.cli.subcommands.dataset import DatasetSubcommand
20
19
  from materials_commons.cli.subcommands.down import down_subcommand
21
- from materials_commons.cli.subcommands.down2 import down2_subcommand
22
20
  from materials_commons.cli.subcommands.expt import ExptSubcommand
23
21
  from materials_commons.cli.subcommands.fetch import fetch_subcommand
24
22
  # from materials_commons.cli.subcommands.samp import SampSubcommand
25
23
  from materials_commons.cli.subcommands.globus import globus_subcommand
26
24
  from materials_commons.cli.subcommands.init import init_subcommand
27
25
  from materials_commons.cli.subcommands.ls import ls_subcommand
28
- from materials_commons.cli.subcommands.ls2 import ls2_subcommand
29
- from materials_commons.cli.subcommands.mcp import mcp_subcommand
30
26
  from materials_commons.cli.subcommands.mkdir import mkdir_subcommand
31
27
  from materials_commons.cli.subcommands.mv import mv_subcommand
32
28
  # from materials_commons.cli.subcommands.proc import ProcSubcommand
@@ -61,7 +57,6 @@ standard_usage = [
61
57
  # {'name': 'down2', 'desc': 'Donwload files (experimental)', 'subcommand': down2_subcommand},
62
58
  {'name': 'config', 'desc': 'Configure `mc`', 'subcommand': config_subcommand},
63
59
  {'name': 'server', 'desc': 'Run MC Server', 'subcommand': server_subcommand},
64
- {'name': 'mcp', 'desc': 'Run MCP Server', 'subcommand': mcp_subcommand},
65
60
  ]
66
61
  standard_interfaces = {d['name']: d for d in standard_usage}
67
62
 
@@ -169,7 +164,7 @@ def main(argv=None, working_dir=None):
169
164
 
170
165
  if args.command in standard_interfaces:
171
166
  result = standard_interfaces[args.command]['subcommand'](argv[2:], working_dir)
172
- check_package_version()
167
+ # check_package_version()
173
168
  return result
174
169
 
175
170
  else:
@@ -209,6 +209,21 @@ class FileUploader:
209
209
  logger.error(f"Transfer rejected: {reason}")
210
210
  return False
211
211
 
212
+ elif msg["command"] == "TRANSFER_ALREADY_UPLOADED":
213
+ payload = msg["payload"]
214
+ updated_record = replace(self.upload_request.updated_record,
215
+ local_last_seen_ts=int(time.time()),
216
+ remote_checksum=payload.get("file_checksum", ""),
217
+ remote_size=payload.get("file_size", 0),
218
+ remote_file_id=payload.get("file_id", 0),
219
+ remote_ctime_ns=payload.get("file_created_at_ns", 0))
220
+ db_write_request = DBWriteRequest(project=self.upload_request.project,
221
+ data=updated_record,
222
+ command="single")
223
+ await self.db_write_queue.put(db_write_request)
224
+ self._already_uploaded = True
225
+ return False
226
+
212
227
  logger.error(f"Unexpected response: {msg['command']}")
213
228
  return False
214
229
 
@@ -241,7 +241,7 @@ class WebSocketCommandListener:
241
241
 
242
242
  headers["MC-Client-ID"] = self.client_uuid
243
243
  headers["MC-Client-Hostname"] = socket.gethostname()
244
- headers["MC-Connection-Type"] = "cli"
244
+ headers["MC-Connection-Type"] = "pycli"
245
245
 
246
246
  try:
247
247
  projects = server.list_local_projects()
@@ -192,6 +192,9 @@ async def ws_upload(args, working_dir):
192
192
  ignore_parser = igittigitt.IgnoreParser()
193
193
  ignore_parser.parse_rule_files(base_dir=proj.local_path, filename=".mcignore", add_default_patterns=False)
194
194
 
195
+ def ignore(path, _ignore):
196
+ return ignore_parser.match(path)
197
+
195
198
  # Start services
196
199
  container = ServiceContainer.create(ws_url=args.ws_url)
197
200
  service_runtime = ServiceRuntime(container)
@@ -204,7 +207,7 @@ async def ws_upload(args, working_dir):
204
207
  p = Path(path)
205
208
  if p.is_dir():
206
209
  async for current_path, path_entries in async_reconciler.walk(path=path, listdir_fn=local_listdir,
207
- recursive=args.recursive, ignore_fn=None):
210
+ recursive=args.recursive, ignore_fn=ignore):
208
211
  for entry_name in sorted(path_entries):
209
212
  file_state = path_entries[entry_name]
210
213
  if file_state.exception:
@@ -218,6 +221,8 @@ async def ws_upload(args, working_dir):
218
221
  transfer_id = await container.file_upload_manager.upload_file(upload_request)
219
222
  transfer_ids.append(transfer_id)
220
223
  elif p.is_file():
224
+ if ignore(p, True):
225
+ continue
221
226
  file_state = await async_reconciler.reconcile_file(p)
222
227
  if file_state.exception:
223
228
  logger.error(f"Error encountered while processing {p}: {file_state.exception}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: materials_commons_cli
3
- Version: 2.3.0b11
3
+ Version: 2.3.0b12
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
@@ -27,7 +27,6 @@ Requires-Dist: requests~=2.28
27
27
  Requires-Dist: sortedcontainers~=2.4
28
28
  Requires-Dist: tabulate~=0.9
29
29
  Requires-Dist: aiosqlite~=0.22
30
- Requires-Dist: fastmcp~=3.4
31
30
  Dynamic: author
32
31
  Dynamic: author-email
33
32
  Dynamic: classifier
@@ -69,7 +69,6 @@ materials_commons/cli/subcommands/globus.py
69
69
  materials_commons/cli/subcommands/init.py
70
70
  materials_commons/cli/subcommands/ls.py
71
71
  materials_commons/cli/subcommands/ls2.py
72
- materials_commons/cli/subcommands/mcp.py
73
72
  materials_commons/cli/subcommands/mkdir.py
74
73
  materials_commons/cli/subcommands/mv.py
75
74
  materials_commons/cli/subcommands/proj.py
@@ -10,4 +10,3 @@ requests~=2.28
10
10
  sortedcontainers~=2.4
11
11
  tabulate~=0.9
12
12
  aiosqlite~=0.22
13
- fastmcp~=3.4
@@ -71,6 +71,5 @@ setup(
71
71
  "sortedcontainers~=2.4",
72
72
  "tabulate~=0.9",
73
73
  "aiosqlite~=0.22",
74
- "fastmcp~=3.4",
75
74
  ]
76
75
  )
@@ -7,11 +7,13 @@ from unittest.mock import AsyncMock
7
7
  import pytest
8
8
 
9
9
  from materials_commons.cli.server.uploader.file_uploader import FileUploader
10
+ from materials_commons.cli.subcommands.up import ws_upload
10
11
 
11
12
 
12
13
  @dataclass(frozen=True)
13
14
  class FakeUpdatedRecord:
14
15
  local_checksum: str = "local-checksum"
16
+ local_last_seen_ts: int = 0
15
17
  remote_checksum: str = ""
16
18
  remote_size: int = 0
17
19
  remote_file_id: int = 0
@@ -81,6 +83,276 @@ def drain_queue(queue):
81
83
  items.append(queue.get_nowait())
82
84
  return items
83
85
 
86
+ def test_wait_for_acceptance_already_uploaded_writes_updated_record_to_db_queue(tmp_path):
87
+ """
88
+ Intent:
89
+ Verify TRANSFER_ALREADY_UPLOADED updates the file database queue.
90
+
91
+ When the server reports that a file has already been uploaded, the uploader
92
+ should treat acceptance as complete, mark the upload as already uploaded, and
93
+ enqueue the updated remote metadata for persistence.
94
+ """
95
+
96
+ async def run_test():
97
+ uploader, _, db_write_queue = make_uploader(tmp_path, file_bytes=b"hello")
98
+
99
+ await uploader.handle_response(
100
+ {
101
+ "command": "TRANSFER_ALREADY_UPLOADED",
102
+ "payload": {
103
+ "file_checksum": "remote-checksum",
104
+ "file_size": 5,
105
+ "file_id": 4321,
106
+ "file_created_at_ns": 987654321,
107
+ },
108
+ }
109
+ )
110
+
111
+ result = await uploader._wait_for_acceptance()
112
+
113
+ assert result is False
114
+ assert uploader._already_uploaded is True
115
+ assert uploader.waiting_for_response is None
116
+
117
+ db_write_request = await db_write_queue.get()
118
+ assert db_write_request.project == uploader.upload_request.project
119
+ assert db_write_request.command == "single"
120
+ assert db_write_request.data.remote_checksum == "remote-checksum"
121
+ assert db_write_request.data.remote_size == 5
122
+ assert db_write_request.data.remote_file_id == 4321
123
+ assert db_write_request.data.remote_ctime_ns == 987654321
124
+
125
+ asyncio.run(run_test())
126
+
127
+
128
+ def test_upload_returns_true_and_updates_db_when_server_says_already_uploaded(tmp_path):
129
+ """
130
+ Intent:
131
+ Verify the upload-level TRANSFER_ALREADY_UPLOADED path.
132
+
133
+ upload() should return success without sending chunks or TRANSFER_COMPLETE,
134
+ and it should preserve the database update produced during acceptance.
135
+ """
136
+
137
+ async def run_test():
138
+ uploader, ws_send_queue, db_write_queue = make_uploader(
139
+ tmp_path,
140
+ file_bytes=b"hello",
141
+ )
142
+
143
+ await uploader.handle_response(
144
+ {
145
+ "command": "TRANSFER_ALREADY_UPLOADED",
146
+ "payload": {
147
+ "file_checksum": "remote-checksum",
148
+ "file_size": 5,
149
+ "file_id": 2468,
150
+ "file_created_at_ns": 13579,
151
+ },
152
+ }
153
+ )
154
+
155
+ result = await uploader.upload()
156
+
157
+ assert result is True
158
+
159
+ sent_messages = drain_queue(ws_send_queue)
160
+ assert len(sent_messages) == 1
161
+ assert sent_messages[0]["command"] == "TRANSFER_INIT"
162
+
163
+ db_write_request = await db_write_queue.get()
164
+ assert db_write_request.command == "single"
165
+ assert db_write_request.data.remote_checksum == "remote-checksum"
166
+ assert db_write_request.data.remote_size == 5
167
+ assert db_write_request.data.remote_file_id == 2468
168
+ assert db_write_request.data.remote_ctime_ns == 13579
169
+
170
+ asyncio.run(run_test())
171
+
172
+
173
+ def test_ws_upload_single_file_in_mcignore_is_not_uploaded_or_reconciled(tmp_path, monkeypatch):
174
+ """
175
+ Intent:
176
+ Verify websocket single-file upload honors .mcignore.
177
+
178
+ If the requested file matches .mcignore, ws_upload() should skip it before
179
+ reconciliation and should not enqueue an upload.
180
+ """
181
+
182
+ async def run_test():
183
+ project_root = tmp_path
184
+ ignored_file = project_root / "ignored.txt"
185
+ ignored_file.write_text("do not upload")
186
+ (project_root / ".mcignore").write_text("ignored.txt\n")
187
+
188
+ db = SimpleNamespace(close=AsyncMock())
189
+ proj = SimpleNamespace(local_path=project_root, get_filedb=AsyncMock(return_value=db))
190
+
191
+ class FakeLocalProject:
192
+ @staticmethod
193
+ def load(_working_dir):
194
+ return proj
195
+
196
+ class FakeServiceRuntime:
197
+ def __init__(self, _container):
198
+ pass
199
+
200
+ async def start(self, websocket_listener=True):
201
+ return None
202
+
203
+ async def stop(self, drain=True):
204
+ return None
205
+
206
+ upload_file = AsyncMock()
207
+
208
+ fake_container = SimpleNamespace(
209
+ file_upload_manager=SimpleNamespace(
210
+ upload_file=upload_file,
211
+ wait_all=AsyncMock(),
212
+ )
213
+ )
214
+
215
+ class FakeServiceContainer:
216
+ @staticmethod
217
+ def create(ws_url):
218
+ return fake_container
219
+
220
+ reconcile_file = AsyncMock()
221
+
222
+ class FakeAsyncReconciler:
223
+ def __init__(self, db, proj, reconcile_mode):
224
+ pass
225
+
226
+ async def reconcile_file(self, path):
227
+ return await reconcile_file(path)
228
+
229
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.LocalProject", FakeLocalProject)
230
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.ServiceRuntime", FakeServiceRuntime)
231
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.ServiceContainer", FakeServiceContainer)
232
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.AsyncReconciler", FakeAsyncReconciler)
233
+
234
+ args = SimpleNamespace(
235
+ paths=[ignored_file.as_posix()],
236
+ recursive=False,
237
+ ws_url="ws://example.test",
238
+ )
239
+
240
+ await ws_upload(args, project_root)
241
+
242
+ reconcile_file.assert_not_awaited()
243
+ upload_file.assert_not_awaited()
244
+ fake_container.file_upload_manager.wait_all.assert_awaited_once()
245
+ db.close.assert_awaited_once()
246
+
247
+ asyncio.run(run_test())
248
+
249
+
250
+ def test_ws_upload_directory_walk_does_not_upload_files_in_mcignore(tmp_path, monkeypatch):
251
+ """
252
+ Intent:
253
+ Verify websocket directory upload honors .mcignore during walk.
254
+
255
+ The directory walk should receive and use the ignore function, causing ignored
256
+ files to be excluded from upload while non-ignored files are still uploaded.
257
+ """
258
+
259
+ async def run_test():
260
+ project_root = tmp_path
261
+ allowed_file = project_root / "allowed.txt"
262
+ ignored_file = project_root / "ignored.txt"
263
+ ignored_dir = project_root / "ignored-dir"
264
+ ignored_dir_file = ignored_dir / "nested.txt"
265
+
266
+ allowed_file.write_text("upload me")
267
+ ignored_file.write_text("do not upload me")
268
+ ignored_dir.mkdir()
269
+ ignored_dir_file.write_text("do not walk me")
270
+ (project_root / ".mcignore").write_text("ignored.txt\nignored-dir/\n")
271
+
272
+ db = SimpleNamespace(close=AsyncMock())
273
+ proj = SimpleNamespace(local_path=project_root, get_filedb=AsyncMock(return_value=db))
274
+
275
+ class FakeLocalProject:
276
+ @staticmethod
277
+ def load(_working_dir):
278
+ return proj
279
+
280
+ class FakeServiceRuntime:
281
+ def __init__(self, _container):
282
+ pass
283
+
284
+ async def start(self, websocket_listener=True):
285
+ return None
286
+
287
+ async def stop(self, drain=True):
288
+ return None
289
+
290
+ upload_file = AsyncMock(side_effect=["transfer-allowed"])
291
+
292
+ fake_container = SimpleNamespace(
293
+ file_upload_manager=SimpleNamespace(
294
+ upload_file=upload_file,
295
+ wait_all=AsyncMock(),
296
+ )
297
+ )
298
+
299
+ class FakeServiceContainer:
300
+ @staticmethod
301
+ def create(ws_url):
302
+ return fake_container
303
+
304
+ observed_ignore_results = {}
305
+
306
+ class FakeAsyncReconciler:
307
+ def __init__(self, db, proj, reconcile_mode):
308
+ pass
309
+
310
+ async def walk(self, path, listdir_fn, recursive=False, ignore_fn=None):
311
+ observed_ignore_results["allowed_file"] = ignore_fn(allowed_file, False)
312
+ observed_ignore_results["ignored_file"] = ignore_fn(ignored_file, False)
313
+ observed_ignore_results["ignored_dir"] = ignore_fn(ignored_dir, True)
314
+ observed_ignore_results["ignored_dir_file"] = ignore_fn(ignored_dir_file, False)
315
+
316
+ yield Path(path), {
317
+ "allowed.txt": SimpleNamespace(
318
+ exception=None,
319
+ observation=SimpleNamespace(name="allowed.txt"),
320
+ file_decision=SimpleNamespace(
321
+ action="upload",
322
+ updated_record=FakeUpdatedRecord(local_checksum="allowed-checksum"),
323
+ ),
324
+ )
325
+ }
326
+
327
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.LocalProject", FakeLocalProject)
328
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.ServiceRuntime", FakeServiceRuntime)
329
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.ServiceContainer", FakeServiceContainer)
330
+ monkeypatch.setattr("materials_commons.cli.subcommands.up.AsyncReconciler", FakeAsyncReconciler)
331
+
332
+ args = SimpleNamespace(
333
+ paths=[project_root.as_posix()],
334
+ recursive=True,
335
+ ws_url="ws://example.test",
336
+ )
337
+
338
+ await ws_upload(args, project_root)
339
+
340
+ assert observed_ignore_results == {
341
+ "allowed_file": False,
342
+ "ignored_file": True,
343
+ "ignored_dir": True,
344
+ "ignored_dir_file": True,
345
+ }
346
+
347
+ upload_file.assert_awaited_once()
348
+ upload_request = upload_file.await_args.args[0]
349
+ assert upload_request.observation.name == "allowed.txt"
350
+ assert upload_request.updated_record.local_checksum == "allowed-checksum"
351
+
352
+ fake_container.file_upload_manager.wait_all.assert_awaited_once()
353
+ db.close.assert_awaited_once()
354
+
355
+ asyncio.run(run_test())
84
356
 
85
357
  def test_send_transfer_init_puts_expected_message_on_websocket_queue(tmp_path):
86
358
  """
@@ -1 +0,0 @@
1
- __version__ = '2.3.0b11'
@@ -1,86 +0,0 @@
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)