flwr 1.23.0__py3-none-any.whl → 1.25.0__py3-none-any.whl
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.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +19 -0
- flwr/cli/{new/templates → app_cmd}/__init__.py +9 -1
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +262 -0
- flwr/cli/auth_plugin/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- flwr/cli/config_utils.py +101 -13
- flwr/cli/{new/templates/app/code/flwr_tune → federation}/__init__.py +10 -1
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +318 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +211 -130
- flwr/cli/new/new.py +123 -331
- flwr/cli/pull.py +10 -5
- flwr/cli/run/run.py +71 -29
- flwr/cli/run_utils.py +148 -0
- flwr/cli/stop.py +26 -8
- flwr/cli/supernode/ls.py +25 -12
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +239 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +8 -9
- flwr/client/grpc_rere_client/connection.py +16 -14
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +18 -18
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +19 -0
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +11 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +58 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +24 -14
- flwr/proto/fleet_pb2.pyi +141 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +158 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +39 -17
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +2 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +75 -30
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +148 -149
- flwr/server/superlink/linkstate/linkstate.py +91 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +502 -436
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +8 -8
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +34 -1
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +190 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- flwr/{cli/new/templates/app → superlink/federation}/__init__.py +10 -1
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/control_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +7 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +190 -23
- flwr/supernode/cli/flower_supernode.py +58 -3
- flwr/supernode/nodestate/in_memory_nodestate.py +121 -49
- flwr/supernode/nodestate/nodestate.py +52 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +46 -10
- flwr/supernode/start_client_internal.py +165 -46
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/METADATA +9 -11
- flwr-1.25.0.dist-info/RECORD +393 -0
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py +0 -15
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.0.dist-info → flwr-1.25.0.dist-info}/entry_points.txt +0 -0
flwr/cli/new/new.py
CHANGED
|
@@ -16,92 +16,84 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import io
|
|
19
|
-
import json
|
|
20
|
-
import re
|
|
21
19
|
import zipfile
|
|
22
|
-
from enum import Enum
|
|
23
20
|
from pathlib import Path
|
|
24
|
-
from
|
|
25
|
-
from typing import Annotated, Optional
|
|
21
|
+
from typing import Annotated, cast
|
|
26
22
|
|
|
27
23
|
import requests
|
|
28
24
|
import typer
|
|
29
25
|
|
|
30
|
-
from flwr.supercore.constant import
|
|
26
|
+
from flwr.supercore.constant import PLATFORM_API_URL
|
|
27
|
+
from flwr.supercore.utils import parse_app_spec, request_download_link
|
|
31
28
|
|
|
32
|
-
from ..utils import
|
|
33
|
-
is_valid_project_name,
|
|
34
|
-
prompt_options,
|
|
35
|
-
prompt_text,
|
|
36
|
-
sanitize_project_name,
|
|
37
|
-
)
|
|
29
|
+
from ..utils import prompt_options, prompt_text
|
|
38
30
|
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if not tpl_file_path.is_file():
|
|
75
|
-
raise TemplateNotFound(f"Template '{name}' not found")
|
|
76
|
-
|
|
77
|
-
with open(tpl_file_path, encoding="utf-8") as tpl_file:
|
|
78
|
-
return tpl_file.read()
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def render_template(template: str, data: dict[str, str]) -> str:
|
|
82
|
-
"""Render template."""
|
|
83
|
-
tpl_file = load_template(template)
|
|
84
|
-
tpl = Template(tpl_file)
|
|
85
|
-
if ".gitignore" not in template:
|
|
86
|
-
return tpl.substitute(data)
|
|
87
|
-
return tpl.template
|
|
88
|
-
|
|
32
|
+
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
33
|
+
def new(
|
|
34
|
+
app_spec: Annotated[
|
|
35
|
+
str | None,
|
|
36
|
+
typer.Argument(
|
|
37
|
+
help="Flower app specifier. Use the format "
|
|
38
|
+
"'@account_name/app_name' or '@account_name/app_name==x.y.z'. "
|
|
39
|
+
"Version is optional (defaults to latest)."
|
|
40
|
+
),
|
|
41
|
+
] = None,
|
|
42
|
+
framework: Annotated[
|
|
43
|
+
str | None,
|
|
44
|
+
typer.Option(case_sensitive=False, help="Deprecated. The ML framework to use"),
|
|
45
|
+
] = None,
|
|
46
|
+
username: Annotated[
|
|
47
|
+
str | None,
|
|
48
|
+
typer.Option(
|
|
49
|
+
case_sensitive=False, help="Deprecated. The Flower username of the author"
|
|
50
|
+
),
|
|
51
|
+
] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Create new Flower App."""
|
|
54
|
+
if framework is not None or username is not None:
|
|
55
|
+
typer.secho(
|
|
56
|
+
"❌ The --framework and --username options are deprecated and will be "
|
|
57
|
+
"removed in future versions of Flower. Please provide an app specifier "
|
|
58
|
+
"after `flwr new` instead, e.g., '@account_name/app_name' or "
|
|
59
|
+
"'@account_name/app_name==x.y.z'.",
|
|
60
|
+
fg=typer.colors.RED,
|
|
61
|
+
bold=True,
|
|
62
|
+
err=True,
|
|
63
|
+
)
|
|
64
|
+
raise typer.Exit(code=1)
|
|
89
65
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
66
|
+
if app_spec is None:
|
|
67
|
+
# Fetch recommended apps
|
|
68
|
+
print(
|
|
69
|
+
typer.style(
|
|
70
|
+
"\n🌸 Fetching recommended apps...",
|
|
71
|
+
fg=typer.colors.GREEN,
|
|
72
|
+
bold=True,
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
apps = fetch_recommended_apps()
|
|
94
76
|
|
|
77
|
+
if not apps:
|
|
78
|
+
typer.secho(
|
|
79
|
+
"No recommended apps found. Please provide an app specifier manually.",
|
|
80
|
+
fg=typer.colors.YELLOW,
|
|
81
|
+
)
|
|
82
|
+
app_spec = prompt_text("Please provide the app specifier")
|
|
83
|
+
else:
|
|
84
|
+
# Extract app_ids and show selection menu
|
|
85
|
+
app_ids = [app["app_id"] for app in apps]
|
|
86
|
+
app_spec = prompt_options(
|
|
87
|
+
"Select a Flower App to create by entering "
|
|
88
|
+
"the number from the list below:",
|
|
89
|
+
app_ids,
|
|
90
|
+
)
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
content = render_template(template, context)
|
|
99
|
-
create_file(file_path, content)
|
|
92
|
+
# Download remote app
|
|
93
|
+
download_remote_app_via_api(app_spec)
|
|
100
94
|
|
|
101
95
|
|
|
102
|
-
def print_success_prompt(
|
|
103
|
-
package_name: str, llm_challenge_str: Optional[str] = None
|
|
104
|
-
) -> None:
|
|
96
|
+
def print_success_prompt(package_name: str) -> None:
|
|
105
97
|
"""Print styled setup instructions for running a new Flower App after creation."""
|
|
106
98
|
prompt = typer.style(
|
|
107
99
|
"🎊 Flower App creation successful.\n\n"
|
|
@@ -110,10 +102,8 @@ def print_success_prompt(
|
|
|
110
102
|
bold=True,
|
|
111
103
|
)
|
|
112
104
|
|
|
113
|
-
_add = " huggingface-cli login\n" if llm_challenge_str else ""
|
|
114
|
-
|
|
115
105
|
prompt += typer.style(
|
|
116
|
-
f" cd {package_name} && pip install -e .\n
|
|
106
|
+
f" cd {package_name} && pip install -e .\n\n",
|
|
117
107
|
fg=typer.colors.BRIGHT_CYAN,
|
|
118
108
|
bold=True,
|
|
119
109
|
)
|
|
@@ -140,6 +130,25 @@ def print_success_prompt(
|
|
|
140
130
|
print(prompt)
|
|
141
131
|
|
|
142
132
|
|
|
133
|
+
def fetch_recommended_apps() -> list[dict[str, str]]:
|
|
134
|
+
"""Fetch recommended apps from Platform API."""
|
|
135
|
+
url = f"{PLATFORM_API_URL}/hub/apps?tag=recommended"
|
|
136
|
+
try:
|
|
137
|
+
response = requests.get(url, headers={"accept": "application/json"}, timeout=10)
|
|
138
|
+
response.raise_for_status()
|
|
139
|
+
data = response.json()
|
|
140
|
+
apps = data.get("apps", [])
|
|
141
|
+
return cast(list[dict[str, str]], apps)
|
|
142
|
+
|
|
143
|
+
except requests.RequestException as e:
|
|
144
|
+
typer.secho(
|
|
145
|
+
f"❌ Failed to fetch recommended apps: {e}",
|
|
146
|
+
fg=typer.colors.RED,
|
|
147
|
+
err=True,
|
|
148
|
+
)
|
|
149
|
+
raise typer.Exit(code=1) from e
|
|
150
|
+
|
|
151
|
+
|
|
143
152
|
# Security: prevent zip-slip
|
|
144
153
|
def _safe_extract_zip(zf: zipfile.ZipFile, dest_dir: Path) -> None:
|
|
145
154
|
"""Extract ZIP file into destination directory."""
|
|
@@ -181,55 +190,36 @@ def _download_zip_to_memory(presigned_url: str) -> io.BytesIO:
|
|
|
181
190
|
r = requests.get(presigned_url, timeout=60)
|
|
182
191
|
r.raise_for_status()
|
|
183
192
|
except requests.RequestException as e:
|
|
184
|
-
|
|
193
|
+
typer.secho(
|
|
194
|
+
f"ZIP download failed: {e}",
|
|
195
|
+
fg=typer.colors.RED,
|
|
196
|
+
err=True,
|
|
197
|
+
)
|
|
198
|
+
raise typer.Exit(code=1) from e
|
|
185
199
|
|
|
186
200
|
buf = io.BytesIO(r.content)
|
|
187
201
|
# Validate it's a zip
|
|
188
202
|
if not zipfile.is_zipfile(buf):
|
|
189
|
-
|
|
203
|
+
typer.secho(
|
|
204
|
+
"Downloaded file is not a valid ZIP",
|
|
205
|
+
fg=typer.colors.RED,
|
|
206
|
+
err=True,
|
|
207
|
+
)
|
|
208
|
+
raise typer.Exit(code=1)
|
|
190
209
|
buf.seek(0)
|
|
191
210
|
return buf
|
|
192
211
|
|
|
193
212
|
|
|
194
|
-
def
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
headers = {
|
|
198
|
-
"Content-Type": "application/json",
|
|
199
|
-
"Accept": "application/json",
|
|
200
|
-
}
|
|
201
|
-
body = {
|
|
202
|
-
"identifier": identifier, # send raw string of identifier
|
|
203
|
-
}
|
|
204
|
-
|
|
213
|
+
def download_remote_app_via_api(app_spec: str) -> None:
|
|
214
|
+
"""Download App from Platform API."""
|
|
215
|
+
# Validate app version and ID format
|
|
205
216
|
try:
|
|
206
|
-
|
|
207
|
-
except
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if resp.status_code == 404:
|
|
211
|
-
raise typer.BadParameter(f"'{identifier}' not found in Platform API")
|
|
212
|
-
if not resp.ok:
|
|
213
|
-
raise typer.BadParameter(
|
|
214
|
-
f"Platform API request failed with "
|
|
215
|
-
f"status {resp.status_code}. Details: {resp.text}"
|
|
216
|
-
)
|
|
217
|
+
app_id, app_version = parse_app_spec(app_spec)
|
|
218
|
+
except ValueError as e:
|
|
219
|
+
typer.secho(f"❌ {e}", fg=typer.colors.RED, err=True)
|
|
220
|
+
raise typer.Exit(code=1) from e
|
|
217
221
|
|
|
218
|
-
|
|
219
|
-
if "zip_url" not in data:
|
|
220
|
-
raise typer.BadParameter("Invalid response from Platform API")
|
|
221
|
-
return str(data["zip_url"])
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def download_remote_app_via_api(identifier: str) -> None:
|
|
225
|
-
"""Download App from Platform API."""
|
|
226
|
-
# Parse @user/app just to derive local dir name
|
|
227
|
-
m = re.match(APP_ID_PATTERN, identifier)
|
|
228
|
-
if not m:
|
|
229
|
-
raise typer.BadParameter(
|
|
230
|
-
"Invalid remote app ID. Expected format: '@user_name/app_name'."
|
|
231
|
-
)
|
|
232
|
-
app_name = m.group("app")
|
|
222
|
+
app_name = app_id.split("/")[1]
|
|
233
223
|
|
|
234
224
|
project_dir = Path.cwd() / app_name
|
|
235
225
|
if project_dir.exists():
|
|
@@ -242,230 +232,32 @@ def download_remote_app_via_api(identifier: str) -> None:
|
|
|
242
232
|
):
|
|
243
233
|
return
|
|
244
234
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
bold=True,
|
|
250
|
-
)
|
|
235
|
+
typer.secho(
|
|
236
|
+
f"\n🔗 Requesting download link for {app_id}...",
|
|
237
|
+
fg=typer.colors.GREEN,
|
|
238
|
+
bold=True,
|
|
251
239
|
)
|
|
252
|
-
|
|
240
|
+
# Fetch ZIP downloading URL
|
|
241
|
+
url = f"{PLATFORM_API_URL}/hub/fetch-zip"
|
|
242
|
+
try:
|
|
243
|
+
presigned_url, _ = request_download_link(app_id, app_version, url, "zip_url")
|
|
244
|
+
except ValueError as e:
|
|
245
|
+
typer.secho(f"❌ {e}", fg=typer.colors.RED, err=True)
|
|
246
|
+
raise typer.Exit(code=1) from e
|
|
253
247
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
bold=True,
|
|
259
|
-
)
|
|
248
|
+
typer.secho(
|
|
249
|
+
"🔽 Downloading ZIP into memory...",
|
|
250
|
+
fg=typer.colors.GREEN,
|
|
251
|
+
bold=True,
|
|
260
252
|
)
|
|
261
253
|
zip_buf = _download_zip_to_memory(presigned_url)
|
|
262
254
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
bold=True,
|
|
268
|
-
)
|
|
255
|
+
typer.secho(
|
|
256
|
+
f"📦 Unpacking into {project_dir}...",
|
|
257
|
+
fg=typer.colors.GREEN,
|
|
258
|
+
bold=True,
|
|
269
259
|
)
|
|
270
260
|
with zipfile.ZipFile(zip_buf) as zf:
|
|
271
261
|
_safe_extract_zip(zf, Path.cwd())
|
|
272
262
|
|
|
273
263
|
print_success_prompt(app_name)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
277
|
-
def new(
|
|
278
|
-
app_name: Annotated[
|
|
279
|
-
Optional[str],
|
|
280
|
-
typer.Argument(help="The name of the Flower App"),
|
|
281
|
-
] = None,
|
|
282
|
-
framework: Annotated[
|
|
283
|
-
Optional[MlFramework],
|
|
284
|
-
typer.Option(case_sensitive=False, help="The ML framework to use"),
|
|
285
|
-
] = None,
|
|
286
|
-
username: Annotated[
|
|
287
|
-
Optional[str],
|
|
288
|
-
typer.Option(case_sensitive=False, help="The Flower username of the author"),
|
|
289
|
-
] = None,
|
|
290
|
-
) -> None:
|
|
291
|
-
"""Create new Flower App."""
|
|
292
|
-
if app_name is None:
|
|
293
|
-
app_name = prompt_text("Please provide the app name")
|
|
294
|
-
|
|
295
|
-
# Download remote app
|
|
296
|
-
if app_name and app_name.startswith("@"):
|
|
297
|
-
download_remote_app_via_api(app_name)
|
|
298
|
-
return
|
|
299
|
-
|
|
300
|
-
if not is_valid_project_name(app_name):
|
|
301
|
-
app_name = prompt_text(
|
|
302
|
-
"Please provide a name that only contains "
|
|
303
|
-
"characters in {'-', a-zA-Z', '0-9'}",
|
|
304
|
-
predicate=is_valid_project_name,
|
|
305
|
-
default=sanitize_project_name(app_name),
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
# Set project directory path
|
|
309
|
-
package_name = re.sub(r"[-_.]+", "-", app_name).lower()
|
|
310
|
-
import_name = package_name.replace("-", "_")
|
|
311
|
-
project_dir = Path.cwd() / package_name
|
|
312
|
-
|
|
313
|
-
if project_dir.exists():
|
|
314
|
-
if not typer.confirm(
|
|
315
|
-
typer.style(
|
|
316
|
-
f"\n💬 {app_name} already exists, do you want to override it?",
|
|
317
|
-
fg=typer.colors.MAGENTA,
|
|
318
|
-
bold=True,
|
|
319
|
-
)
|
|
320
|
-
):
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
if username is None:
|
|
324
|
-
username = prompt_text("Please provide your Flower username")
|
|
325
|
-
|
|
326
|
-
if framework is not None:
|
|
327
|
-
framework_str = str(framework.value)
|
|
328
|
-
else:
|
|
329
|
-
framework_str = prompt_options(
|
|
330
|
-
"Please select ML framework by typing in the number",
|
|
331
|
-
[mlf.value for mlf in MlFramework],
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
llm_challenge_str = None
|
|
335
|
-
if framework_str == MlFramework.FLOWERTUNE:
|
|
336
|
-
llm_challenge_value = prompt_options(
|
|
337
|
-
"Please select LLM challenge by typing in the number",
|
|
338
|
-
sorted([challenge.value for challenge in LlmChallengeName]),
|
|
339
|
-
)
|
|
340
|
-
llm_challenge_str = llm_challenge_value.lower()
|
|
341
|
-
|
|
342
|
-
if framework_str == MlFramework.BASELINE:
|
|
343
|
-
framework_str = "baseline"
|
|
344
|
-
|
|
345
|
-
if framework_str == MlFramework.PYTORCH_LEGACY_API:
|
|
346
|
-
framework_str = "pytorch_legacy_api"
|
|
347
|
-
|
|
348
|
-
print(
|
|
349
|
-
typer.style(
|
|
350
|
-
f"\n🔨 Creating Flower App {app_name}...",
|
|
351
|
-
fg=typer.colors.GREEN,
|
|
352
|
-
bold=True,
|
|
353
|
-
)
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
context = {
|
|
357
|
-
"framework_str": framework_str,
|
|
358
|
-
"import_name": import_name.replace("-", "_"),
|
|
359
|
-
"package_name": package_name,
|
|
360
|
-
"project_name": app_name,
|
|
361
|
-
"username": username,
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
template_name = framework_str.lower()
|
|
365
|
-
|
|
366
|
-
# List of files to render
|
|
367
|
-
if llm_challenge_str:
|
|
368
|
-
files = {
|
|
369
|
-
".gitignore": {"template": "app/.gitignore.tpl"},
|
|
370
|
-
"pyproject.toml": {"template": f"app/pyproject.{template_name}.toml.tpl"},
|
|
371
|
-
"README.md": {"template": f"app/README.{template_name}.md.tpl"},
|
|
372
|
-
f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"},
|
|
373
|
-
f"{import_name}/server_app.py": {
|
|
374
|
-
"template": "app/code/flwr_tune/server_app.py.tpl"
|
|
375
|
-
},
|
|
376
|
-
f"{import_name}/client_app.py": {
|
|
377
|
-
"template": "app/code/flwr_tune/client_app.py.tpl"
|
|
378
|
-
},
|
|
379
|
-
f"{import_name}/models.py": {
|
|
380
|
-
"template": "app/code/flwr_tune/models.py.tpl"
|
|
381
|
-
},
|
|
382
|
-
f"{import_name}/dataset.py": {
|
|
383
|
-
"template": "app/code/flwr_tune/dataset.py.tpl"
|
|
384
|
-
},
|
|
385
|
-
f"{import_name}/strategy.py": {
|
|
386
|
-
"template": "app/code/flwr_tune/strategy.py.tpl"
|
|
387
|
-
},
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
# Challenge specific context
|
|
391
|
-
fraction_train = "0.2" if llm_challenge_str == "code" else "0.1"
|
|
392
|
-
if llm_challenge_str == "generalnlp":
|
|
393
|
-
challenge_name = "General NLP"
|
|
394
|
-
num_clients = "20"
|
|
395
|
-
dataset_name = "flwrlabs/alpaca-gpt4"
|
|
396
|
-
elif llm_challenge_str == "finance":
|
|
397
|
-
challenge_name = "Finance"
|
|
398
|
-
num_clients = "50"
|
|
399
|
-
dataset_name = "flwrlabs/fingpt-sentiment-train"
|
|
400
|
-
elif llm_challenge_str == "medical":
|
|
401
|
-
challenge_name = "Medical"
|
|
402
|
-
num_clients = "20"
|
|
403
|
-
dataset_name = "flwrlabs/medical-meadow-medical-flashcards"
|
|
404
|
-
else:
|
|
405
|
-
challenge_name = "Code"
|
|
406
|
-
num_clients = "10"
|
|
407
|
-
dataset_name = "flwrlabs/code-alpaca-20k"
|
|
408
|
-
|
|
409
|
-
context["llm_challenge_str"] = llm_challenge_str
|
|
410
|
-
context["fraction_train"] = fraction_train
|
|
411
|
-
context["challenge_name"] = challenge_name
|
|
412
|
-
context["num_clients"] = num_clients
|
|
413
|
-
context["dataset_name"] = dataset_name
|
|
414
|
-
else:
|
|
415
|
-
files = {
|
|
416
|
-
".gitignore": {"template": "app/.gitignore.tpl"},
|
|
417
|
-
"README.md": {"template": "app/README.md.tpl"},
|
|
418
|
-
"pyproject.toml": {"template": f"app/pyproject.{template_name}.toml.tpl"},
|
|
419
|
-
f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"},
|
|
420
|
-
f"{import_name}/server_app.py": {
|
|
421
|
-
"template": f"app/code/server.{template_name}.py.tpl"
|
|
422
|
-
},
|
|
423
|
-
f"{import_name}/client_app.py": {
|
|
424
|
-
"template": f"app/code/client.{template_name}.py.tpl"
|
|
425
|
-
},
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
# Depending on the framework, generate task.py file
|
|
429
|
-
frameworks_with_tasks = [
|
|
430
|
-
MlFramework.PYTORCH.value,
|
|
431
|
-
MlFramework.JAX.value,
|
|
432
|
-
MlFramework.HUGGINGFACE.value,
|
|
433
|
-
MlFramework.MLX.value,
|
|
434
|
-
MlFramework.TENSORFLOW.value,
|
|
435
|
-
MlFramework.SKLEARN.value,
|
|
436
|
-
MlFramework.NUMPY.value,
|
|
437
|
-
MlFramework.XGBOOST.value,
|
|
438
|
-
"pytorch_legacy_api",
|
|
439
|
-
]
|
|
440
|
-
if framework_str in frameworks_with_tasks:
|
|
441
|
-
files[f"{import_name}/task.py"] = {
|
|
442
|
-
"template": f"app/code/task.{template_name}.py.tpl"
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if framework_str == "pytorch_legacy_api":
|
|
446
|
-
# Use custom __init__ that better captures name of framework
|
|
447
|
-
files[f"{import_name}/__init__.py"] = {
|
|
448
|
-
"template": f"app/code/__init__.{framework_str}.py.tpl"
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if framework_str == "baseline":
|
|
452
|
-
# Include additional files for baseline template
|
|
453
|
-
for file_name in ["model", "dataset", "strategy", "utils", "__init__"]:
|
|
454
|
-
files[f"{import_name}/{file_name}.py"] = {
|
|
455
|
-
"template": f"app/code/{file_name}.{template_name}.py.tpl"
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
# Replace README.md
|
|
459
|
-
files["README.md"]["template"] = f"app/README.{template_name}.md.tpl"
|
|
460
|
-
|
|
461
|
-
# Add LICENSE
|
|
462
|
-
files["LICENSE"] = {"template": "app/LICENSE.tpl"}
|
|
463
|
-
|
|
464
|
-
for file_path, value in files.items():
|
|
465
|
-
render_and_create(
|
|
466
|
-
file_path=project_dir / file_path,
|
|
467
|
-
template=value["template"],
|
|
468
|
-
context=context,
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
print_success_prompt(package_name, llm_challenge_str)
|
flwr/cli/pull.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Annotated
|
|
19
|
+
from typing import Annotated
|
|
20
20
|
|
|
21
21
|
import typer
|
|
22
22
|
|
|
@@ -50,22 +50,26 @@ def pull( # pylint: disable=R0914
|
|
|
50
50
|
typer.Argument(help="Path of the Flower App to run."),
|
|
51
51
|
] = Path("."),
|
|
52
52
|
federation: Annotated[
|
|
53
|
-
|
|
53
|
+
str | None,
|
|
54
54
|
typer.Argument(help="Name of the federation."),
|
|
55
55
|
] = None,
|
|
56
56
|
federation_config_overrides: Annotated[
|
|
57
|
-
|
|
57
|
+
list[str] | None,
|
|
58
58
|
typer.Option(
|
|
59
59
|
"--federation-config",
|
|
60
60
|
help=FEDERATION_CONFIG_HELP_MESSAGE,
|
|
61
61
|
),
|
|
62
62
|
] = None,
|
|
63
63
|
) -> None:
|
|
64
|
-
"""Pull artifacts from a Flower run.
|
|
64
|
+
"""Pull artifacts from a Flower run.
|
|
65
|
+
|
|
66
|
+
Retrieve a download URL for artifacts generated during a completed Flower run. The
|
|
67
|
+
artifacts can then be downloaded from the provided URL.
|
|
68
|
+
"""
|
|
65
69
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
|
66
70
|
|
|
67
71
|
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
|
68
|
-
config, errors, warnings = load_and_validate(
|
|
72
|
+
config, errors, warnings = load_and_validate(pyproject_path, check_module=False)
|
|
69
73
|
config = process_loaded_project_config(config, errors, warnings)
|
|
70
74
|
federation, federation_config = validate_federation_in_project_config(
|
|
71
75
|
federation, config, federation_config_overrides
|
|
@@ -88,6 +92,7 @@ def pull( # pylint: disable=R0914
|
|
|
88
92
|
"obtained.",
|
|
89
93
|
fg=typer.colors.RED,
|
|
90
94
|
bold=True,
|
|
95
|
+
err=True,
|
|
91
96
|
)
|
|
92
97
|
raise typer.Exit(code=1)
|
|
93
98
|
|