mcpywrap-core 0.6.0__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.
- mcpywrap_core-0.6.0/LICENSE +21 -0
- mcpywrap_core-0.6.0/MANIFEST.in +6 -0
- mcpywrap_core-0.6.0/PKG-INFO +33 -0
- mcpywrap_core-0.6.0/README.md +11 -0
- mcpywrap_core-0.6.0/THIRD_PARTY_NOTICES.md +35 -0
- mcpywrap_core-0.6.0/mcpywrap_core/__init__.py +2 -0
- mcpywrap_core-0.6.0/mcpywrap_core/__main__.py +18 -0
- mcpywrap_core-0.6.0/mcpywrap_core/_script_worker.py +278 -0
- mcpywrap_core-0.6.0/mcpywrap_core/_version.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/api.py +69 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/AddonsPack.py +310 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/MapPack.py +171 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/__init__.py +20 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/assembly.py +100 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/dependency_manager.py +239 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/file_merge.py +260 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/project_builder.py +182 -0
- mcpywrap_core-0.6.0/mcpywrap_core/builders/watcher.py +162 -0
- mcpywrap_core-0.6.0/mcpywrap_core/cli.py +75 -0
- mcpywrap_core-0.6.0/mcpywrap_core/code_libraries.py +201 -0
- mcpywrap_core-0.6.0/mcpywrap_core/command_context.py +242 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/add_cmd.py +76 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/build_cmd.py +15 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/connect_cmd.py +46 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/default_cmd.py +14 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/dependency_prompt.py +45 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/deployment_cmd.py +115 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/deployment_output.py +289 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/desktop_cmd.py +104 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/dev_cmd.py +108 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/doctor_cmd.py +32 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/edit_cmd.py +109 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/engine_cmd.py +90 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/init_cmd.py +48 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/mod_cmd.py +53 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/modsdk_cmd.py +41 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/package_cmd.py +16 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/publish_cmd.py +51 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/record_cmd.py +85 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/remove_cmd.py +62 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/run_cmd.py +169 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/runtime_cmd.py +150 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/runtime_input_cmd.py +86 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/runtime_player_cmd.py +192 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/runtime_ui_cmd.py +121 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/script_cmd.py +54 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/serve_cmd.py +39 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/session_cmd.py +95 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/sync_cmd.py +29 -0
- mcpywrap_core-0.6.0/mcpywrap_core/commands/ui_cmd.py +36 -0
- mcpywrap_core-0.6.0/mcpywrap_core/config.py +254 -0
- mcpywrap_core-0.6.0/mcpywrap_core/consumer_prepare.py +471 -0
- mcpywrap_core-0.6.0/mcpywrap_core/core-contract.json +6 -0
- mcpywrap_core-0.6.0/mcpywrap_core/custom_packaging.py +41 -0
- mcpywrap_core-0.6.0/mcpywrap_core/dependencies.py +326 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/android_icon.py +186 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/apk_profile.py +90 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/backend.py +134 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/host.py +32 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/install.py +665 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/macos.py +483 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/macos_icons.py +159 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/macos_network.py +58 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/official.py +129 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/preferences.py +146 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/tui.py +110 -0
- mcpywrap_core-0.6.0/mcpywrap_core/engines/windows.py +144 -0
- mcpywrap_core-0.6.0/mcpywrap_core/framework_presets.py +22 -0
- mcpywrap_core-0.6.0/mcpywrap_core/frameworks.py +77 -0
- mcpywrap_core-0.6.0/mcpywrap_core/git_cache.py +154 -0
- mcpywrap_core-0.6.0/mcpywrap_core/git_projects.py +266 -0
- mcpywrap_core-0.6.0/mcpywrap_core/host.py +93 -0
- mcpywrap_core-0.6.0/mcpywrap_core/host_assets.py +14 -0
- mcpywrap_core-0.6.0/mcpywrap_core/input_executor.py +246 -0
- mcpywrap_core-0.6.0/mcpywrap_core/input_plan.py +257 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/SimpleMonitor.py +51 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/auth_interaction.py +88 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/bridge_assets.py +85 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/deployment_links.py +670 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/diagnostics.py +21 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/discovery.py +337 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/editor.py +81 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/file_logs.py +66 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/game.py +256 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/host_input.py +252 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/hot_reload.py +218 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/image.py +20 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/input_sequence.py +204 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/launcher_control.py +56 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/launcher_python.py +134 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/log_colorizer.py +252 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/log_protocol.py +74 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/log_stream.py +136 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/mcs.py +149 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/mcs_auth.py +196 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/network.py +187 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/private_logs.py +70 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/processes.py +41 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/recording_worker.py +154 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/recordings.py +351 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_cppconfig.py +249 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_debug.py +437 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_input_payload.py +277 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_key_timeline_payload.py +229 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_player_payload.py +713 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_ui.py +212 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_ui_outline.py +424 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/runtime_ui_payload.py +560 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/screenshot.py +94 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/session_capabilities.py +54 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/session_worker.py +177 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/sessions.py +285 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/studio_server.py +363 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/studio_server_ui.py +126 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/symlink_helper_global.py +75 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/symlink_helper_map.py +114 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/symlinks.py +534 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/unified_input.py +39 -0
- mcpywrap_core-0.6.0/mcpywrap_core/mcstudio/window.py +755 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/addons.py +148 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/level_dat.py +958 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/map.py +85 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/modsdk_environment.py +197 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/netease_modsdk.py +269 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/template/generate_mod_files.py +116 -0
- mcpywrap_core-0.6.0/mcpywrap_core/minecraft/template/mod_template.py +494 -0
- mcpywrap_core-0.6.0/mcpywrap_core/operations/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/operations/build.py +66 -0
- mcpywrap_core-0.6.0/mcpywrap_core/operations/package.py +97 -0
- mcpywrap_core-0.6.0/mcpywrap_core/operations/sync.py +82 -0
- mcpywrap_core-0.6.0/mcpywrap_core/prepare_environment.py +149 -0
- mcpywrap_core-0.6.0/mcpywrap_core/project_dependencies.py +164 -0
- mcpywrap_core-0.6.0/mcpywrap_core/project_init.py +73 -0
- mcpywrap_core-0.6.0/mcpywrap_core/project_scripts.py +369 -0
- mcpywrap_core-0.6.0/mcpywrap_core/recording_io.py +122 -0
- mcpywrap_core-0.6.0/mcpywrap_core/remote/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/remote/client.py +271 -0
- mcpywrap_core-0.6.0/mcpywrap_core/remote/http_server.py +205 -0
- mcpywrap_core-0.6.0/mcpywrap_core/remote/protocol.py +22 -0
- mcpywrap_core-0.6.0/mcpywrap_core/remote/service.py +317 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/__init__.py +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/deployment.py +501 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/deployment_inspection.py +645 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/deployment_service.py +688 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/diagnostics.py +74 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/groups.py +666 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/instances.py +147 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/native.py +64 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/occupancy.py +78 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/preparation.py +97 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/profiles.py +183 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/storage.py +42 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/targets.py +24 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/watch.py +158 -0
- mcpywrap_core-0.6.0/mcpywrap_core/runtime/windows.py +184 -0
- mcpywrap_core-0.6.0/mcpywrap_core/source_files.py +82 -0
- mcpywrap_core-0.6.0/mcpywrap_core/timeline.py +26 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/__init__.py +5 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/application.py +58 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/assets/mcpy.icns +0 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/assets/mcpy.icon/Assets/01-cube.svg +5 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/assets/mcpy.icon/Assets/02-prompt.svg +4 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/assets/mcpy.icon/icon.json +56 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/assets/mcpy.png +0 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/engine_setup.py +119 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/log_view.py +201 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/log_window.py +147 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/project_ui.py +963 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/run_session.py +89 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/session_controller.py +416 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/session_tools.py +76 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/session_views.py +236 -0
- mcpywrap_core-0.6.0/mcpywrap_core/ui/world_config.py +112 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/__init__.py +6 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/console.py +13 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/link_snapshot.py +131 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/locks.py +85 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/paths.py +18 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/pip_error_parser.py +184 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/print_guide.py +30 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/project_setup.py +148 -0
- mcpywrap_core-0.6.0/mcpywrap_core/utils/utils.py +38 -0
- mcpywrap_core-0.6.0/mcpywrap_core.egg-info/PKG-INFO +33 -0
- mcpywrap_core-0.6.0/mcpywrap_core.egg-info/SOURCES.txt +193 -0
- mcpywrap_core-0.6.0/mcpywrap_core.egg-info/dependency_links.txt +1 -0
- mcpywrap_core-0.6.0/mcpywrap_core.egg-info/requires.txt +11 -0
- mcpywrap_core-0.6.0/mcpywrap_core.egg-info/top_level.txt +2 -0
- mcpywrap_core-0.6.0/mcpywrap_host/__init__.py +9 -0
- mcpywrap_core-0.6.0/mcpywrap_host/store.py +276 -0
- mcpywrap_core-0.6.0/pyproject.toml +35 -0
- mcpywrap_core-0.6.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EaseCation®️
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
include README.md LICENSE THIRD_PARTY_NOTICES.md
|
|
2
|
+
include mcpywrap_core/core-contract.json
|
|
3
|
+
recursive-include mcpywrap_core/ui/assets *.svg *.png *.icns *.json
|
|
4
|
+
prune mcpywrap_core/mcstudio/bridge_payload
|
|
5
|
+
prune mcpywrap_core/mcstudio/window_capture
|
|
6
|
+
global-exclude __pycache__ *.py[cod] *.exe *.dll *.so *.dylib *.pyd
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcpywrap-core
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Shared mcpy project dependency, build and runtime logic
|
|
5
|
+
Author-email: boybook <boybook@easecation.net>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EaseCation/mcpywrap
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: THIRD_PARTY_NOTICES.md
|
|
12
|
+
Requires-Dist: click>=8
|
|
13
|
+
Requires-Dist: packaging>=21
|
|
14
|
+
Requires-Dist: tomli>=2
|
|
15
|
+
Requires-Dist: tomli-w>=1
|
|
16
|
+
Provides-Extra: watch
|
|
17
|
+
Requires-Dist: watchdog>=2; extra == "watch"
|
|
18
|
+
Provides-Extra: prepare
|
|
19
|
+
Requires-Dist: jsonschema<5,>=4.18; extra == "prepare"
|
|
20
|
+
Requires-Dist: psutil>=5.8; extra == "prepare"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# mcpywrap-core
|
|
24
|
+
|
|
25
|
+
Shared engineering and runtime implementation for mcpywrap. Install `mcpywrap`
|
|
26
|
+
for the complete desktop tool and the `mcpy` command. Install `mcpywrap-core`
|
|
27
|
+
for the minimal Python library used by embedded hosts.
|
|
28
|
+
|
|
29
|
+
Both distributions share a repository, release version and implementation.
|
|
30
|
+
The public library entry point is `mcpywrap_core.api.Core`. Optional groups are
|
|
31
|
+
`watch` and `prepare`; platform-specific capabilities are supplied by the host.
|
|
32
|
+
|
|
33
|
+
Documentation: https://github.com/EaseCation/mcpywrap/blob/main/docs/shared-core.md
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# mcpywrap-core
|
|
2
|
+
|
|
3
|
+
Shared engineering and runtime implementation for mcpywrap. Install `mcpywrap`
|
|
4
|
+
for the complete desktop tool and the `mcpy` command. Install `mcpywrap-core`
|
|
5
|
+
for the minimal Python library used by embedded hosts.
|
|
6
|
+
|
|
7
|
+
Both distributions share a repository, release version and implementation.
|
|
8
|
+
The public library entry point is `mcpywrap_core.api.Core`. Optional groups are
|
|
9
|
+
`watch` and `prepare`; platform-specific capabilities are supplied by the host.
|
|
10
|
+
|
|
11
|
+
Documentation: https://github.com/EaseCation/mcpywrap/blob/main/docs/shared-core.md
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# GUI dependency notices
|
|
2
|
+
|
|
3
|
+
mcpywrap's own source code is distributed under the MIT License in `LICENSE`.
|
|
4
|
+
This notice covers the Qt GUI dependencies, not the complete dependency tree.
|
|
5
|
+
Other installed packages retain their own copyright and license notices.
|
|
6
|
+
|
|
7
|
+
## PySide6, Shiboken6 and Qt
|
|
8
|
+
|
|
9
|
+
The Windows and macOS GUI uses the official Qt for Python bindings, installed
|
|
10
|
+
as `PySide6-Essentials` and its `shiboken6` dependency. The application uses
|
|
11
|
+
QtCore, QtGui and QtWidgets; it does not require PyQt or PySide6-Addons.
|
|
12
|
+
|
|
13
|
+
We select the LGPLv3 licensing option for these dependencies. The inspected
|
|
14
|
+
6.11.2 distributions declare `LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only`.
|
|
15
|
+
The Qt libraries used by the application are available under LGPLv3.
|
|
16
|
+
Copyright and additional third-party notices remain with the upstream packages.
|
|
17
|
+
|
|
18
|
+
The mcpywrap wheel does not bundle Qt, PySide6 or Shiboken6 binaries. Package
|
|
19
|
+
managers install them separately in the user's Python environment. Users may
|
|
20
|
+
replace compatible versions; mcpywrap does not prevent modification or debugging
|
|
21
|
+
of these libraries. Retain upstream notices and provide the applicable
|
|
22
|
+
corresponding-source access when redistributing these dependencies. A future
|
|
23
|
+
frozen application or a change in Qt modules requires a fresh distribution audit.
|
|
24
|
+
|
|
25
|
+
Upstream licensing and source:
|
|
26
|
+
|
|
27
|
+
- https://doc.qt.io/qtforpython-6/licenses.html
|
|
28
|
+
- https://code.qt.io/cgit/pyside/pyside-setup.git/
|
|
29
|
+
- https://code.qt.io/cgit/qt/qtbase.git/
|
|
30
|
+
|
|
31
|
+
## Native game launcher
|
|
32
|
+
|
|
33
|
+
The separately downloaded native launcher has its own GPL licensing and
|
|
34
|
+
corresponding-source distribution. Changing the Python GUI binding does not
|
|
35
|
+
change that launcher's license or permit redistribution of Minecraft binaries.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
mcpywrap 命令行工具的入口点
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .cli import cli
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
"""主函数,作为 CLI 入口点"""
|
|
10
|
+
import sys
|
|
11
|
+
# Keep the caller's text encoding; unsupported symbols must not abort an operation.
|
|
12
|
+
for stream in (sys.stdout, sys.stderr):
|
|
13
|
+
if hasattr(stream, 'reconfigure'):
|
|
14
|
+
stream.reconfigure(errors='backslashreplace')
|
|
15
|
+
cli()
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Private finite-script supervisor. Invoked by absolute path; never a public CLI."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import signal
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
import psutil
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def identity(pid):
|
|
14
|
+
p = psutil.Process(pid)
|
|
15
|
+
return {'pid': pid, 'created_at': p.create_time(), 'executable': p.exe()}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def alive(record):
|
|
19
|
+
try:
|
|
20
|
+
p = psutil.Process(record['pid'])
|
|
21
|
+
return abs(p.create_time() - record['created_at']) < .01 and p.status() != psutil.STATUS_ZOMBIE
|
|
22
|
+
except psutil.NoSuchProcess:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def save(path, data):
|
|
27
|
+
temporary = path.with_suffix('.writing')
|
|
28
|
+
temporary.write_text(json.dumps(data, ensure_ascii=True), encoding='utf-8')
|
|
29
|
+
for attempt in range(10):
|
|
30
|
+
try:
|
|
31
|
+
temporary.replace(path)
|
|
32
|
+
return
|
|
33
|
+
except PermissionError:
|
|
34
|
+
if os.name != 'nt' or attempt == 9: raise
|
|
35
|
+
time.sleep(.02)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class WindowsJob:
|
|
39
|
+
"""Closing the supervisor's handle terminates its assigned command tree."""
|
|
40
|
+
def __init__(self):
|
|
41
|
+
import ctypes as c
|
|
42
|
+
from ctypes import wintypes as w
|
|
43
|
+
class Basic(c.Structure):
|
|
44
|
+
_fields_ = [('PerProcessUserTimeLimit', c.c_longlong), ('PerJobUserTimeLimit', c.c_longlong),
|
|
45
|
+
('LimitFlags', w.DWORD), ('MinimumWorkingSetSize', c.c_size_t),
|
|
46
|
+
('MaximumWorkingSetSize', c.c_size_t), ('ActiveProcessLimit', w.DWORD),
|
|
47
|
+
('Affinity', c.c_size_t), ('PriorityClass', w.DWORD), ('SchedulingClass', w.DWORD)]
|
|
48
|
+
class IO(c.Structure):
|
|
49
|
+
_fields_ = [(name, c.c_ulonglong) for name in ('ReadOperationCount', 'WriteOperationCount',
|
|
50
|
+
'OtherOperationCount', 'ReadTransferCount', 'WriteTransferCount', 'OtherTransferCount')]
|
|
51
|
+
class Extended(c.Structure):
|
|
52
|
+
_fields_ = [('BasicLimitInformation', Basic), ('IoInfo', IO),
|
|
53
|
+
('ProcessMemoryLimit', c.c_size_t), ('JobMemoryLimit', c.c_size_t),
|
|
54
|
+
('PeakProcessMemoryUsed', c.c_size_t), ('PeakJobMemoryUsed', c.c_size_t)]
|
|
55
|
+
self.kernel = c.WinDLL('kernel32', use_last_error=True)
|
|
56
|
+
self.kernel.CreateJobObjectW.argtypes = [c.c_void_p, w.LPCWSTR]
|
|
57
|
+
self.kernel.CreateJobObjectW.restype = w.HANDLE
|
|
58
|
+
self.kernel.SetInformationJobObject.argtypes = [w.HANDLE, c.c_int, c.c_void_p, w.DWORD]
|
|
59
|
+
self.kernel.AssignProcessToJobObject.argtypes = [w.HANDLE, w.HANDLE]
|
|
60
|
+
self.kernel.QueryInformationJobObject.argtypes = [w.HANDLE, c.c_int, c.c_void_p, w.DWORD, c.c_void_p]
|
|
61
|
+
self.kernel.TerminateJobObject.argtypes = [w.HANDLE, w.UINT]
|
|
62
|
+
self.kernel.OpenProcess.argtypes = [w.DWORD, w.BOOL, w.DWORD]
|
|
63
|
+
self.kernel.OpenProcess.restype = w.HANDLE
|
|
64
|
+
self.kernel.IsProcessInJob.argtypes = [w.HANDLE, w.HANDLE, c.POINTER(w.BOOL)]
|
|
65
|
+
self.kernel.WaitForSingleObject.argtypes = [w.HANDLE, w.DWORD]
|
|
66
|
+
self.kernel.WaitForSingleObject.restype = w.DWORD
|
|
67
|
+
self.kernel.CloseHandle.argtypes = [w.HANDLE]
|
|
68
|
+
self.kernel.OpenThread.argtypes = [w.DWORD, w.BOOL, w.DWORD]
|
|
69
|
+
self.kernel.OpenThread.restype = w.HANDLE
|
|
70
|
+
self.kernel.ResumeThread.argtypes = [w.HANDLE]
|
|
71
|
+
self.kernel.ResumeThread.restype = w.DWORD
|
|
72
|
+
self.handle = self.kernel.CreateJobObjectW(None, None)
|
|
73
|
+
if not self.handle: raise c.WinError(c.get_last_error())
|
|
74
|
+
info = Extended(); info.BasicLimitInformation.LimitFlags = 0x2000 # KILL_ON_JOB_CLOSE
|
|
75
|
+
if not self.kernel.SetInformationJobObject(self.handle, 9, c.byref(info), c.sizeof(info)):
|
|
76
|
+
error = c.WinError(c.get_last_error()); self.close(); raise error
|
|
77
|
+
|
|
78
|
+
def assign(self, process):
|
|
79
|
+
import ctypes as c
|
|
80
|
+
if not self.kernel.AssignProcessToJobObject(self.handle, int(process._handle)):
|
|
81
|
+
raise c.WinError(c.get_last_error())
|
|
82
|
+
# The command was created suspended: no child can escape before job assignment.
|
|
83
|
+
threads = psutil.Process(process.pid).threads()
|
|
84
|
+
if not threads:
|
|
85
|
+
raise RuntimeError('脚本主线程不存在')
|
|
86
|
+
thread = self.kernel.OpenThread(0x0002, False, threads[0].id)
|
|
87
|
+
if not thread:
|
|
88
|
+
raise c.WinError(c.get_last_error())
|
|
89
|
+
try:
|
|
90
|
+
if self.kernel.ResumeThread(thread) == 0xffffffff:
|
|
91
|
+
raise c.WinError(c.get_last_error())
|
|
92
|
+
finally:
|
|
93
|
+
self.kernel.CloseHandle(thread)
|
|
94
|
+
|
|
95
|
+
def close(self):
|
|
96
|
+
if self.handle:
|
|
97
|
+
self.kernel.CloseHandle(self.handle)
|
|
98
|
+
self.handle = None
|
|
99
|
+
|
|
100
|
+
def process_ids(self):
|
|
101
|
+
import ctypes as c
|
|
102
|
+
from ctypes import wintypes as w
|
|
103
|
+
capacity = 16
|
|
104
|
+
for _ in range(8):
|
|
105
|
+
class ProcessIds(c.Structure):
|
|
106
|
+
_fields_ = [('assigned', w.DWORD), ('listed', w.DWORD),
|
|
107
|
+
('ids', c.c_size_t * capacity)]
|
|
108
|
+
info = ProcessIds()
|
|
109
|
+
# JobObjectBasicProcessIdList retains descendants after their
|
|
110
|
+
# parent exits. A concurrent spawn may require a larger buffer.
|
|
111
|
+
if self.kernel.QueryInformationJobObject(self.handle, 3, c.byref(info), c.sizeof(info), None):
|
|
112
|
+
return list(info.ids[:info.listed])
|
|
113
|
+
error = c.get_last_error()
|
|
114
|
+
if error != 234: # ERROR_MORE_DATA
|
|
115
|
+
raise c.WinError(error)
|
|
116
|
+
capacity = max(capacity * 2, info.assigned)
|
|
117
|
+
raise RuntimeError('脚本进程树持续增长,无法确认成员')
|
|
118
|
+
|
|
119
|
+
def active_count(self):
|
|
120
|
+
import ctypes as c
|
|
121
|
+
from ctypes import wintypes as w
|
|
122
|
+
count = 0
|
|
123
|
+
for pid in self.process_ids():
|
|
124
|
+
handle = self.kernel.OpenProcess(0x00100000 | 0x1000, False, pid) # SYNCHRONIZE | QUERY_LIMITED_INFORMATION
|
|
125
|
+
if not handle:
|
|
126
|
+
error = c.get_last_error()
|
|
127
|
+
if error == 87: # ERROR_INVALID_PARAMETER: process already gone
|
|
128
|
+
continue
|
|
129
|
+
raise c.WinError(error)
|
|
130
|
+
try:
|
|
131
|
+
belongs = w.BOOL()
|
|
132
|
+
if not self.kernel.IsProcessInJob(handle, self.handle, c.byref(belongs)):
|
|
133
|
+
raise c.WinError(c.get_last_error())
|
|
134
|
+
if not belongs.value:
|
|
135
|
+
continue # The PID may have been reused outside this job.
|
|
136
|
+
state = self.kernel.WaitForSingleObject(handle, 0)
|
|
137
|
+
if state == 258: # WAIT_TIMEOUT: process is still running
|
|
138
|
+
count += 1
|
|
139
|
+
elif state != 0: # WAIT_OBJECT_0: process has exited
|
|
140
|
+
raise c.WinError(c.get_last_error())
|
|
141
|
+
finally:
|
|
142
|
+
self.kernel.CloseHandle(handle)
|
|
143
|
+
return count
|
|
144
|
+
|
|
145
|
+
def terminate(self):
|
|
146
|
+
import ctypes as c
|
|
147
|
+
if not self.kernel.TerminateJobObject(self.handle, 1):
|
|
148
|
+
raise c.WinError(c.get_last_error())
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def members(process, known):
|
|
152
|
+
try:
|
|
153
|
+
for p in psutil.Process(process.pid).children(recursive=True):
|
|
154
|
+
known[p.pid] = identity(p.pid)
|
|
155
|
+
except psutil.NoSuchProcess:
|
|
156
|
+
pass
|
|
157
|
+
if os.name != 'nt':
|
|
158
|
+
for p in psutil.process_iter(['pid', 'status']):
|
|
159
|
+
try:
|
|
160
|
+
if p.info['status'] != psutil.STATUS_ZOMBIE and os.getpgid(p.pid) == process.pid:
|
|
161
|
+
known[p.pid] = identity(p.pid)
|
|
162
|
+
except (ProcessLookupError, PermissionError, psutil.NoSuchProcess):
|
|
163
|
+
pass
|
|
164
|
+
return [r for r in known.values() if alive(r)]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def finish_tree(process, known, job):
|
|
168
|
+
if job:
|
|
169
|
+
# Keep the job handle until exit is verified, including descendants
|
|
170
|
+
# never observed by psutil. Closing it alone only requests termination.
|
|
171
|
+
job.terminate()
|
|
172
|
+
if process.poll() is None:
|
|
173
|
+
process.kill() # Also handles failure to assign a suspended child.
|
|
174
|
+
process.wait(timeout=3)
|
|
175
|
+
deadline = time.monotonic() + 2
|
|
176
|
+
while job.active_count():
|
|
177
|
+
if time.monotonic() >= deadline:
|
|
178
|
+
raise RuntimeError('脚本后代进程尚未确认退出')
|
|
179
|
+
time.sleep(.05)
|
|
180
|
+
return
|
|
181
|
+
if os.name != 'nt':
|
|
182
|
+
try: os.killpg(process.pid, signal.SIGTERM)
|
|
183
|
+
except ProcessLookupError: pass
|
|
184
|
+
pending = members(process, known)
|
|
185
|
+
for record in pending:
|
|
186
|
+
try: psutil.Process(record['pid']).terminate()
|
|
187
|
+
except psutil.NoSuchProcess: pass
|
|
188
|
+
deadline = time.monotonic() + 2
|
|
189
|
+
while time.monotonic() < deadline and any(alive(r) for r in pending):
|
|
190
|
+
process.poll()
|
|
191
|
+
time.sleep(.05)
|
|
192
|
+
remaining = members(process, known)
|
|
193
|
+
if not remaining:
|
|
194
|
+
process.wait(timeout=3)
|
|
195
|
+
return
|
|
196
|
+
if os.name != 'nt':
|
|
197
|
+
try: os.killpg(process.pid, signal.SIGKILL)
|
|
198
|
+
except ProcessLookupError: pass
|
|
199
|
+
for record in remaining:
|
|
200
|
+
if alive(record):
|
|
201
|
+
try: psutil.Process(record['pid']).kill()
|
|
202
|
+
except psutil.NoSuchProcess: pass
|
|
203
|
+
process.wait(timeout=3)
|
|
204
|
+
deadline = time.monotonic() + 2
|
|
205
|
+
while time.monotonic() < deadline:
|
|
206
|
+
if not any(alive(r) for r in known.values()): return
|
|
207
|
+
time.sleep(.05)
|
|
208
|
+
raise RuntimeError('脚本后代进程尚未确认退出')
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def main():
|
|
212
|
+
spec = json.loads(sys.stdin.buffer.read())
|
|
213
|
+
path = Path(spec.pop('result'))
|
|
214
|
+
command, timeout = spec.pop('command'), spec.pop('timeout')
|
|
215
|
+
cancel, parent = Path(spec.pop('cancel')), spec.pop('parent')
|
|
216
|
+
state = dict(spec, supervisor=identity(os.getpid()), state='starting')
|
|
217
|
+
process, job, known = None, None, {}
|
|
218
|
+
started = time.monotonic()
|
|
219
|
+
try:
|
|
220
|
+
save(path, state)
|
|
221
|
+
if not alive(parent) or cancel.exists():
|
|
222
|
+
state.update(state='cancelled', code='script_cancelled')
|
|
223
|
+
return
|
|
224
|
+
if os.name == 'nt': job = WindowsJob()
|
|
225
|
+
state['state'] = 'spawning'; save(path, state)
|
|
226
|
+
process = subprocess.Popen(command, stdin=subprocess.DEVNULL,
|
|
227
|
+
**({'creationflags': subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP | 0x00000004}
|
|
228
|
+
if os.name == 'nt' else {'start_new_session': True}))
|
|
229
|
+
try:
|
|
230
|
+
child = identity(process.pid)
|
|
231
|
+
except psutil.ZombieProcess:
|
|
232
|
+
child = {'pid': process.pid, 'created_at': psutil.Process(process.pid).create_time(), 'executable': command[0]}
|
|
233
|
+
state.update(child=child, group=process.pid if os.name != 'nt' else None, state='running')
|
|
234
|
+
known[process.pid] = child
|
|
235
|
+
save(path, state)
|
|
236
|
+
if job: job.assign(process)
|
|
237
|
+
while process.poll() is None:
|
|
238
|
+
if not job:
|
|
239
|
+
members(process, known)
|
|
240
|
+
if cancel.exists() or not alive(parent):
|
|
241
|
+
state.update(state='cancelled', code='script_cancelled', error='操作已取消或调用进程已退出')
|
|
242
|
+
break
|
|
243
|
+
if time.monotonic() - started >= timeout:
|
|
244
|
+
state.update(state='timed_out', code='script_timeout', error='执行超时')
|
|
245
|
+
break
|
|
246
|
+
time.sleep(.05)
|
|
247
|
+
else:
|
|
248
|
+
# Windows 的 Job 是所有权依据。父进程退出后再按 PID 查询树,
|
|
249
|
+
# 可能观察到复用该 PID 的无关进程;同时给短暂的退出收尾留出时间。
|
|
250
|
+
descendants = [] if job else [r for r in members(process, known) if r['pid'] != process.pid]
|
|
251
|
+
remaining = job.active_count() if job else 0
|
|
252
|
+
settle_deadline = time.monotonic() + .2
|
|
253
|
+
while remaining and time.monotonic() < settle_deadline:
|
|
254
|
+
time.sleep(.01)
|
|
255
|
+
remaining = job.active_count()
|
|
256
|
+
state.update(state='completed' if process.returncode == 0 else 'failed',
|
|
257
|
+
code=None if process.returncode == 0 else 'script_failed', exit_code=process.returncode)
|
|
258
|
+
if descendants or remaining:
|
|
259
|
+
state.update(state='failed', code='script_background_process', error='有限任务不能遗留后台进程')
|
|
260
|
+
except BaseException as exc:
|
|
261
|
+
state.update(state='failed', code='script_start_failed', error=str(exc))
|
|
262
|
+
finally:
|
|
263
|
+
if process:
|
|
264
|
+
try:
|
|
265
|
+
finish_tree(process, known, job)
|
|
266
|
+
except BaseException as exc:
|
|
267
|
+
import traceback
|
|
268
|
+
traceback.print_exc()
|
|
269
|
+
state.update(state='recovery_required', code='script_recovery_required', error=str(exc))
|
|
270
|
+
state.setdefault('exit_code', process.returncode)
|
|
271
|
+
if job: job.close()
|
|
272
|
+
state['duration'] = time.monotonic() - started
|
|
273
|
+
state['finished_at'] = time.time()
|
|
274
|
+
save(path, state)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == '__main__':
|
|
278
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Versioned engineering API. All frontends call the same operation functions."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from . import __version__
|
|
5
|
+
from .host import HOST_API, HostServices, host_scope, current_host
|
|
6
|
+
|
|
7
|
+
CORE_API = 1
|
|
8
|
+
OPERATIONS = ('resolve', 'build', 'package', 'sync')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Core:
|
|
12
|
+
def __init__(self, host=None):
|
|
13
|
+
self.host = host or current_host()
|
|
14
|
+
|
|
15
|
+
def describe(self):
|
|
16
|
+
return {'core_api': CORE_API, 'host_api': HOST_API, 'version': __version__,
|
|
17
|
+
'operations': list(OPERATIONS), 'capabilities': self.host.capabilities()}
|
|
18
|
+
|
|
19
|
+
def execute(self, operation, project, **options):
|
|
20
|
+
"""Execute or raise the original structured error; never invoke another core."""
|
|
21
|
+
if operation not in OPERATIONS:
|
|
22
|
+
raise ValueError('未知工程操作:' + str(operation))
|
|
23
|
+
allowed = {'resolve': set(), 'build': {'merge'}, 'package': {'merge'},
|
|
24
|
+
'sync': {'install', 'migrate_windows_lock'}}[operation]
|
|
25
|
+
if set(options) - allowed or any(type(value) is not bool for value in options.values()):
|
|
26
|
+
raise ValueError('无效的工程操作参数')
|
|
27
|
+
project = Path(project).expanduser().resolve()
|
|
28
|
+
if not project.is_dir() or not (project/'pyproject.toml').is_file():
|
|
29
|
+
raise ValueError('未找到项目 pyproject.toml:' + str(project))
|
|
30
|
+
from .command_context import project_scope
|
|
31
|
+
with host_scope(self.host), project_scope(project) as context:
|
|
32
|
+
if operation == 'resolve':
|
|
33
|
+
from .dependencies import DependencyService
|
|
34
|
+
manager = DependencyService(project).resolve()
|
|
35
|
+
result = {'project': str(project), 'dependencies': [
|
|
36
|
+
{'name': pack.pkg_name, 'path': str(pack.path)}
|
|
37
|
+
for pack in manager.get_all_dependencies().values()], 'warnings': manager.warnings}
|
|
38
|
+
elif operation == 'build':
|
|
39
|
+
from .operations.build import build_project
|
|
40
|
+
result = build_project(project, **options)
|
|
41
|
+
elif operation == 'package':
|
|
42
|
+
from .operations.package import package_project
|
|
43
|
+
result = package_project(project, **options)
|
|
44
|
+
else:
|
|
45
|
+
from .operations.sync import sync_project
|
|
46
|
+
result = sync_project(project, **options)
|
|
47
|
+
if context.warnings:
|
|
48
|
+
result['warnings'] = list(dict.fromkeys([*result.get('warnings', []), *context.warnings]))
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
def request(self, request):
|
|
52
|
+
"""Transport-neutral envelope. Authentication/path grants belong to the host.
|
|
53
|
+
|
|
54
|
+
The host resolves an authorized project ID to a path before calling this
|
|
55
|
+
method. Do not expose raw caller-supplied paths on an unauthenticated port.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
if not isinstance(request, dict) or request.get('core_api') != CORE_API:
|
|
59
|
+
raise ValueError('不兼容的核心协议')
|
|
60
|
+
if request.get('operation') == 'describe':
|
|
61
|
+
return {'ok': True, 'result': self.describe()}
|
|
62
|
+
options = request.get('options', {})
|
|
63
|
+
if not isinstance(options, dict) or not isinstance(request.get('project'), str):
|
|
64
|
+
raise ValueError('无效的工程请求')
|
|
65
|
+
result = self.execute(request.get('operation'), request['project'], **options)
|
|
66
|
+
return {'ok': True, 'result': result, 'core': self.describe()}
|
|
67
|
+
except (ValueError, OSError) as error:
|
|
68
|
+
return {'ok': False, 'error': str(error), 'code': getattr(error, 'code', 'operation_failed'),
|
|
69
|
+
'hint': getattr(error, 'hint', None), 'details': getattr(error, 'details', {})}
|