syft-flwr 0.1.7__py3-none-any.whl → 0.2.1__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.

Potentially problematic release.


This version of syft-flwr might be problematic. Click here for more details.

@@ -1,33 +1,109 @@
1
1
  import asyncio
2
2
  import os
3
- import uuid
3
+ import sys
4
+ import tempfile
4
5
  from pathlib import Path
5
6
 
6
7
  from loguru import logger
8
+ from syft_core import Client
9
+ from syft_crypto import did_path, ensure_bootstrap, get_did_document, private_key_path
7
10
  from syft_rds.client.rds_client import RDSClient
8
- from syft_rds.orchestra import remove_rds_stack_dir, setup_rds_server
9
- from typing_extensions import Union
11
+ from syft_rds.orchestra import SingleRDSStack, remove_rds_stack_dir
12
+ from typing_extensions import Optional, Union
10
13
 
11
14
  from syft_flwr.config import load_flwr_pyproject
15
+ from syft_flwr.consts import SYFT_FLWR_ENCRYPTION_ENABLED
16
+ from syft_flwr.utils import create_temp_client
12
17
 
13
18
 
14
19
  def _setup_mock_rds_clients(
15
20
  project_dir: Path, aggregator: str, datasites: list[str]
16
- ) -> tuple[str, list[RDSClient], RDSClient]:
21
+ ) -> tuple[Path, list[RDSClient], RDSClient]:
17
22
  """Setup mock RDS clients for the given project directory"""
18
- key = project_dir.name + "_" + str(uuid.uuid4())
19
- remove_rds_stack_dir(key)
23
+ simulated_syftbox_network_dir = Path(tempfile.gettempdir(), project_dir.name)
24
+ remove_rds_stack_dir(root_dir=simulated_syftbox_network_dir)
20
25
 
21
- ds_stack = setup_rds_server(email=aggregator, key=key)
22
- ds_client = ds_stack.init_session(host=aggregator)
26
+ ds_syftbox_client = create_temp_client(
27
+ email=aggregator, workspace_dir=simulated_syftbox_network_dir
28
+ )
29
+ ds_stack = SingleRDSStack(client=ds_syftbox_client)
30
+ ds_rds_client = ds_stack.init_session(host=aggregator)
23
31
 
24
- do_clients = []
32
+ do_rds_clients = []
25
33
  for datasite in datasites:
26
- do_stack = setup_rds_server(email=datasite, key=key)
27
- do_client = do_stack.init_session(host=datasite)
28
- do_clients.append(do_client)
34
+ do_syftbox_client = create_temp_client(
35
+ email=datasite, workspace_dir=simulated_syftbox_network_dir
36
+ )
37
+ do_stack = SingleRDSStack(client=do_syftbox_client)
38
+ do_rds_client = do_stack.init_session(host=datasite)
39
+ do_rds_clients.append(do_rds_client)
40
+
41
+ return simulated_syftbox_network_dir, do_rds_clients, ds_rds_client
42
+
43
+
44
+ def _bootstrap_encryption_keys(
45
+ do_clients: list[RDSClient], ds_client: RDSClient
46
+ ) -> None:
47
+ """Bootstrap the encryption keys for all clients if encryption is enabled."""
48
+ # Check if encryption is enabled
49
+ encryption_enabled = (
50
+ os.environ.get(SYFT_FLWR_ENCRYPTION_ENABLED, "true").lower() != "false"
51
+ )
29
52
 
30
- return key, do_clients, ds_client
53
+ if not encryption_enabled:
54
+ logger.warning("⚠️ Encryption disabled - skipping key bootstrap")
55
+ return
56
+
57
+ logger.info("🔐 Bootstrapping encryption keys for all participants...")
58
+
59
+ all_syftbox_clients: list[Client] = []
60
+
61
+ # Bootstrap server
62
+ try:
63
+ server_client: Client = ds_client._syftbox_client
64
+ ensure_bootstrap(server_client)
65
+ server_client_did_path = did_path(server_client, server_client.email)
66
+ server_client_private_key_path = private_key_path(server_client)
67
+ logger.debug(
68
+ f"✅ Server {ds_client.email} bootstrapped with private encryption keys at {server_client_private_key_path} and did path at {server_client_did_path}"
69
+ )
70
+ all_syftbox_clients.append(server_client)
71
+ except Exception as e:
72
+ logger.error(f"❌ Failed to bootstrap server {ds_client.email}: {e}")
73
+ raise
74
+
75
+ # Bootstrap each client
76
+ for do_client in do_clients:
77
+ try:
78
+ client: Client = do_client._syftbox_client
79
+ ensure_bootstrap(client)
80
+ client_did_path = did_path(client, client.email)
81
+ client_did_doc = get_did_document(client, client.email)
82
+ client_private_key_path = private_key_path(client)
83
+ logger.debug(
84
+ f"✅ Client {do_client.email} bootstrapped with private encryption keys at {client_private_key_path} and did path at {client_did_path} with content: {client_did_doc}"
85
+ )
86
+ all_syftbox_clients.append(client)
87
+ except Exception as e:
88
+ logger.error(f"❌ Failed to bootstrap client {do_client.email}: {e}")
89
+ raise
90
+
91
+ # Verify all DID documents are accessible
92
+ for checking_client in all_syftbox_clients:
93
+ for target_client in all_syftbox_clients:
94
+ if checking_client.email != target_client.email:
95
+ # Verify that checking_client can see target_client's DID document
96
+ did_file_path = did_path(checking_client, target_client.email)
97
+ if did_file_path.exists():
98
+ logger.debug(
99
+ f"✅ {checking_client.email} can see {target_client.email}'s DID at {did_file_path}"
100
+ )
101
+ else:
102
+ logger.warning(
103
+ f"⚠️ {checking_client.email} cannot find {target_client.email}'s DID at {did_file_path}"
104
+ )
105
+
106
+ logger.info("🔐 All participants bootstrapped for E2E encryption ✅✅✅")
31
107
 
32
108
 
33
109
  async def _run_main_py(
@@ -35,7 +111,7 @@ async def _run_main_py(
35
111
  config_path: Path,
36
112
  client_email: str,
37
113
  log_dir: Path,
38
- dataset_path: Union[str, Path] | None = None,
114
+ dataset_path: Optional[Union[str, Path]] = None,
39
115
  ) -> int:
40
116
  """Run the `main.py` file for a given client"""
41
117
  log_file_path = log_dir / f"{client_email}.log"
@@ -49,7 +125,7 @@ async def _run_main_py(
49
125
  try:
50
126
  with open(log_file_path, "w") as f:
51
127
  process = await asyncio.create_subprocess_exec(
52
- "python",
128
+ sys.executable, # Use the current Python executable
53
129
  str(main_py_path),
54
130
  "-s",
55
131
  stdout=f,
@@ -131,7 +207,7 @@ async def _run_simulated_flwr_project(
131
207
  return run_success
132
208
 
133
209
 
134
- def _validate_bootstraped_project(project_dir: Path) -> None:
210
+ def validate_bootstraped_project(project_dir: Path) -> None:
135
211
  """Validate a bootstraped `syft_flwr` project directory"""
136
212
  if not project_dir.exists():
137
213
  raise FileNotFoundError(f"Project directory {project_dir} does not exist")
@@ -159,42 +235,65 @@ def _validate_mock_dataset_paths(mock_dataset_paths: list[str]) -> list[Path]:
159
235
 
160
236
  def run(
161
237
  project_dir: Union[str, Path], mock_dataset_paths: list[Union[str, Path]]
162
- ) -> None:
163
- """Run a syft_flwr project in simulation mode over mock data"""
238
+ ) -> Union[bool, asyncio.Task]:
239
+ """Run a syft_flwr project in simulation mode over mock data.
240
+
241
+ Returns:
242
+ bool: True if simulation succeeded, False otherwise (synchronous execution)
243
+ asyncio.Task: Task handle if running in async environment (e.g., Jupyter)
244
+ """
164
245
 
165
246
  project_dir = Path(project_dir).expanduser().resolve()
166
- _validate_bootstraped_project(project_dir)
247
+ validate_bootstraped_project(project_dir)
167
248
  mock_dataset_paths = _validate_mock_dataset_paths(mock_dataset_paths)
168
249
 
169
- pyproject_conf = load_flwr_pyproject(project_dir)
250
+ # Skip module validation during testing to avoid parallel test issues
251
+ skip_module_check = (
252
+ os.environ.get("SYFT_FLWR_SKIP_MODULE_CHECK", "false").lower() == "true"
253
+ )
254
+ pyproject_conf = load_flwr_pyproject(
255
+ project_dir, check_module=not skip_module_check
256
+ )
170
257
  datasites = pyproject_conf["tool"]["syft_flwr"]["datasites"]
171
258
  aggregator = pyproject_conf["tool"]["syft_flwr"]["aggregator"]
172
259
 
173
- key, do_clients, ds_client = _setup_mock_rds_clients(
260
+ simulated_syftbox_network_dir, do_clients, ds_client = _setup_mock_rds_clients(
174
261
  project_dir, aggregator, datasites
175
262
  )
176
263
 
264
+ _bootstrap_encryption_keys(do_clients, ds_client)
265
+
266
+ simulation_success = False # Track success status
267
+
177
268
  async def main():
269
+ nonlocal simulation_success
178
270
  try:
179
271
  run_success = await _run_simulated_flwr_project(
180
272
  project_dir, do_clients, ds_client, mock_dataset_paths
181
273
  )
274
+ simulation_success = run_success
182
275
  if run_success:
183
276
  logger.success("Simulation completed successfully ✅")
184
277
  else:
185
278
  logger.error("Simulation failed ❌")
186
279
  except Exception as e:
187
280
  logger.error(f"Simulation failed ❌: {e}")
281
+ simulation_success = False
188
282
  finally:
189
283
  # Clean up the RDS stack
190
- remove_rds_stack_dir(key)
191
- logger.debug(f"Removed RDS stack: {key}")
284
+ remove_rds_stack_dir(simulated_syftbox_network_dir)
285
+ logger.debug(f"Removed RDS stack: {simulated_syftbox_network_dir}")
286
+ # Also remove the .syftbox folder that saves the config files and private keys
287
+ remove_rds_stack_dir(simulated_syftbox_network_dir.parent / ".syftbox")
288
+
289
+ return simulation_success
192
290
 
193
291
  try:
194
292
  loop = asyncio.get_running_loop()
195
293
  logger.debug(f"Running in an environment with an existing event loop {loop}")
196
294
  # We are in an environment with an existing event loop (like Jupyter)
197
- asyncio.create_task(main())
295
+ task = asyncio.create_task(main())
296
+ return task # Return the task so callers can await it
198
297
  except RuntimeError:
199
298
  logger.debug("No existing event loop, creating and running one")
200
299
  # No existing event loop, create and run one (for scripts)
@@ -202,3 +301,4 @@ def run(
202
301
  asyncio.set_event_loop(loop)
203
302
  loop.run_until_complete(main())
204
303
  loop.close()
304
+ return simulation_success # Return success status for synchronous execution
syft_flwr/utils.py CHANGED
@@ -3,6 +3,16 @@ import re
3
3
  import zlib
4
4
  from pathlib import Path
5
5
 
6
+ from flwr.common import Metadata
7
+ from flwr.common.message import Error, Message
8
+ from flwr.common.record import RecordDict
9
+ from loguru import logger
10
+ from syft_core import Client, SyftClientConfig
11
+ from syft_crypto.x3dh_bootstrap import ensure_bootstrap
12
+ from typing_extensions import Optional, Tuple
13
+
14
+ from syft_flwr.consts import SYFT_FLWR_ENCRYPTION_ENABLED
15
+
6
16
  EMAIL_REGEX = r"^[^@]+@[^@]+\.[^@]+$"
7
17
 
8
18
 
@@ -34,3 +44,72 @@ def run_syft_flwr() -> bool:
34
44
  return True
35
45
  except FileNotFoundError:
36
46
  return False
47
+
48
+
49
+ def create_temp_client(email: str, workspace_dir: Path) -> Client:
50
+ """Create a temporary Client instance for testing"""
51
+ workspace_hash = hash(str(workspace_dir)) % 10000
52
+ server_port = 8080 + workspace_hash
53
+ client_port = 8082 + workspace_hash
54
+ config: SyftClientConfig = SyftClientConfig(
55
+ email=email,
56
+ data_dir=workspace_dir,
57
+ server_url=f"http://localhost:{server_port}",
58
+ client_url=f"http://localhost:{client_port}",
59
+ path=workspace_dir / ".syftbox" / f"{email.split('@')[0]}_config.json",
60
+ ).save()
61
+ logger.debug(f"Created temp client {email} with config {config}")
62
+ return Client(config)
63
+
64
+
65
+ def setup_client(app_name: str) -> Tuple[Client, bool, str]:
66
+ """Setup SyftBox client and encryption."""
67
+ client = Client.load()
68
+
69
+ # Check encryption setting
70
+ encryption_enabled = (
71
+ os.environ.get(SYFT_FLWR_ENCRYPTION_ENABLED, "true").lower() != "false"
72
+ )
73
+
74
+ # Bootstrap encryption if needed
75
+ if encryption_enabled:
76
+ client = ensure_bootstrap(client)
77
+ logger.info("🔐 End-to-end encryption is ENABLED for FL messages")
78
+ else:
79
+ logger.warning("⚠️ Encryption disabled - skipping client key bootstrap")
80
+ logger.warning(
81
+ "⚠️ End-to-end encryption is DISABLED for FL messages (development mode / insecure)"
82
+ )
83
+
84
+ return client, encryption_enabled, f"flwr/{app_name}"
85
+
86
+
87
+ def check_reply_to_field(metadata: Metadata) -> bool:
88
+ """Check if reply_to field is empty (Flower 1.17+ format)."""
89
+ return metadata.reply_to_message_id == ""
90
+
91
+
92
+ def create_flwr_message(
93
+ content: RecordDict,
94
+ message_type: str,
95
+ dst_node_id: int,
96
+ group_id: str,
97
+ ttl: Optional[float] = None,
98
+ error: Optional[Error] = None,
99
+ reply_to: Optional[Message] = None,
100
+ ) -> Message:
101
+ """Create a Flower message (requires Flower >= 1.17)."""
102
+ if reply_to is not None:
103
+ if error is not None:
104
+ return Message(reply_to=reply_to, error=error)
105
+ return Message(content=content, reply_to=reply_to)
106
+ else:
107
+ if error is not None:
108
+ raise ValueError("Error and reply_to cannot both be None")
109
+ return Message(
110
+ content=content,
111
+ dst_node_id=dst_node_id,
112
+ message_type=message_type,
113
+ ttl=ttl,
114
+ group_id=group_id,
115
+ )
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syft-flwr
3
- Version: 0.1.7
3
+ Version: 0.2.1
4
4
  Summary: syft_flwr is an open source framework that facilitate federated learning projects using Flower over the SyftBox protocol
5
5
  License-File: LICENSE
6
- Requires-Python: >=3.9.2
6
+ Requires-Python: >=3.10
7
7
  Requires-Dist: flwr-datasets[vision]>=0.5.0
8
8
  Requires-Dist: flwr[simulation]>=1.20.0
9
9
  Requires-Dist: loguru>=0.7.3
10
10
  Requires-Dist: safetensors>=0.6.2
11
- Requires-Dist: syft-rds==0.1.5
11
+ Requires-Dist: syft-rds>=0.2.1
12
12
  Requires-Dist: tomli-w>=1.2.0
13
13
  Requires-Dist: tomli>=2.2.1
14
14
  Requires-Dist: typing-extensions>=4.13.0
@@ -0,0 +1,21 @@
1
+ syft_flwr/__init__.py,sha256=BdzYWTvuUs7ihkPtvHRELRjlOoSsnXcXPd63Ys6VpKI,426
2
+ syft_flwr/bootstrap.py,sha256=-T6SRh_p6u6uWpbTPZ6-URsAfMQAI2jakpjZAh0UUlw,3690
3
+ syft_flwr/cli.py,sha256=imctwdQMxQeGQZaiKSX1Mo2nU_-RmA-cGB3H4huuUeA,3274
4
+ syft_flwr/config.py,sha256=4hwkovGtFOLNULjJwoGYcA0uT4y3vZSrxndXqYXquMY,821
5
+ syft_flwr/consts.py,sha256=u3QK-Wp8D2Va7iZcp5z4ormVm_FAUDeK4u-w81UL_eY,107
6
+ syft_flwr/flower_client.py,sha256=dBM8QPJSyQmeoG41w2m9nXM5451VmqUviQz117z4FM4,7066
7
+ syft_flwr/flower_server.py,sha256=ZNDUR1U79M0BaG7n39TGUkVHV2NYi-LDsN8FqKJFfFQ,1508
8
+ syft_flwr/grid.py,sha256=5rbv6ncLKK2S1PAifkavaOG36gih-QexPJHh8nnwLws,20309
9
+ syft_flwr/mounts.py,sha256=hp0TKVot16SaPYO10Y_mSJGei7aiNteJfK4U4vynWmU,2330
10
+ syft_flwr/run.py,sha256=oy_ovAVmO84teKisLYYrY8a5ZmWH8tpWVDwXKB-O0rU,2144
11
+ syft_flwr/run_simulation.py,sha256=frHytbsxYLjiCM4r4m1NVQOc1j98hm4sQQoBLeagJi8,11539
12
+ syft_flwr/serde.py,sha256=5fCI-cRUOh5wE7cXQd4J6jex1grRGnyD1Jx-VlEDOXM,495
13
+ syft_flwr/utils.py,sha256=KYwijACpHOR7pkvezNBqbCE48y3o4G9OUtnvdm1NkaU,3672
14
+ syft_flwr/strategy/__init__.py,sha256=mpUmExjjFkqU8gg41XsOBKfO3aqCBe7XPJSU-_P7smU,97
15
+ syft_flwr/strategy/fedavg.py,sha256=N8jULUkjvuaBIEVINowyQln8W8yFhkO-J8k0-iPcGMA,1562
16
+ syft_flwr/templates/main.py.tpl,sha256=p0uK97jvLGk3LJdy1_HF1R5BQgIjaTGkYnr-csfh39M,791
17
+ syft_flwr-0.2.1.dist-info/METADATA,sha256=3qRf-wHKTe-uZGPY3bzgX3BsoT5Sp4q2FA07TN03TKY,1254
18
+ syft_flwr-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ syft_flwr-0.2.1.dist-info/entry_points.txt,sha256=o7oT0dCoHn-3WyIwdDw1lBh2q-GvhB_8s0hWeJU4myc,49
20
+ syft_flwr-0.2.1.dist-info/licenses/LICENSE,sha256=0msOUar8uPZTqkAOTBp4rCzd7Jl9eRhfKiNufwrsg7k,11361
21
+ syft_flwr-0.2.1.dist-info/RECORD,,
@@ -1,121 +0,0 @@
1
- import flwr
2
- from flwr.common import Metadata
3
- from flwr.common.message import Error, Message
4
- from packaging.version import Version
5
- from typing_extensions import Optional
6
-
7
-
8
- def flwr_later_than_1_17():
9
- return Version(flwr.__version__) >= Version("1.17.0")
10
-
11
-
12
- # Version-dependent imports
13
- if flwr_later_than_1_17():
14
- from flwr.common.record import RecordDict
15
- from flwr.server.grid import Grid
16
- else:
17
- from flwr.common.record import RecordSet as RecordDict
18
- from flwr.server.driver import Driver as Grid
19
-
20
-
21
- __all__ = ["Grid", "RecordDict"]
22
-
23
-
24
- def check_reply_to_field(metadata: Metadata) -> bool:
25
- """Check if reply_to field is empty based on Flower version."""
26
- if flwr_later_than_1_17():
27
- return metadata.reply_to_message_id == ""
28
- else:
29
- return metadata.reply_to_message == ""
30
-
31
-
32
- def create_flwr_message(
33
- content: RecordDict,
34
- message_type: str,
35
- src_node_id: int,
36
- dst_node_id: int,
37
- group_id: str,
38
- run_id: int,
39
- ttl: Optional[float] = None,
40
- error: Optional[Error] = None,
41
- reply_to: Optional[Message] = None,
42
- ) -> Message:
43
- """Create a Flower message with version-compatible parameters."""
44
- if flwr_later_than_1_17():
45
- return _create_message_v1_17_plus(
46
- content,
47
- message_type,
48
- dst_node_id,
49
- group_id,
50
- ttl,
51
- error,
52
- reply_to,
53
- )
54
- else:
55
- return _create_message_pre_v1_17(
56
- content,
57
- message_type,
58
- src_node_id,
59
- dst_node_id,
60
- group_id,
61
- run_id,
62
- ttl,
63
- error,
64
- )
65
-
66
-
67
- def _create_message_v1_17_plus(
68
- content: RecordDict,
69
- message_type: str,
70
- dst_node_id: int,
71
- group_id: str,
72
- ttl: Optional[float],
73
- error: Optional[Error],
74
- reply_to: Optional[Message],
75
- ) -> Message:
76
- """Create message for Flower version 1.17+."""
77
- if reply_to is not None:
78
- if error is not None:
79
- return Message(reply_to=reply_to, error=error)
80
- return Message(content=content, reply_to=reply_to)
81
- else:
82
- if error is not None:
83
- raise ValueError("Error and reply_to cannot both be None")
84
- return Message(
85
- content=content,
86
- dst_node_id=dst_node_id,
87
- message_type=message_type,
88
- ttl=ttl,
89
- group_id=group_id,
90
- )
91
-
92
-
93
- def _create_message_pre_v1_17(
94
- content: RecordDict,
95
- message_type: str,
96
- src_node_id: int,
97
- dst_node_id: int,
98
- group_id: str,
99
- run_id: int,
100
- ttl: Optional[float],
101
- error: Optional[Error],
102
- ) -> Message:
103
- """Create message for Flower versions before 1.17."""
104
- from flwr.common import DEFAULT_TTL
105
-
106
- ttl_ = DEFAULT_TTL if ttl is None else ttl
107
- metadata = Metadata(
108
- run_id=run_id,
109
- message_id="", # Will be set when saving to file
110
- src_node_id=src_node_id,
111
- dst_node_id=dst_node_id,
112
- reply_to_message="",
113
- group_id=group_id,
114
- ttl=ttl_,
115
- message_type=message_type,
116
- )
117
-
118
- if error is not None:
119
- return Message(metadata=metadata, error=error)
120
- else:
121
- return Message(metadata=metadata, content=content)
@@ -1,21 +0,0 @@
1
- syft_flwr/__init__.py,sha256=oGfKpHbO65Irb9RHFJHb7-RyS-rlcszl_tIxbssgdXU,426
2
- syft_flwr/bootstrap.py,sha256=-T6SRh_p6u6uWpbTPZ6-URsAfMQAI2jakpjZAh0UUlw,3690
3
- syft_flwr/cli.py,sha256=imctwdQMxQeGQZaiKSX1Mo2nU_-RmA-cGB3H4huuUeA,3274
4
- syft_flwr/config.py,sha256=4hwkovGtFOLNULjJwoGYcA0uT4y3vZSrxndXqYXquMY,821
5
- syft_flwr/flower_client.py,sha256=5UrtfwSTUDwPQlir7mQWLLVxulGcT4Mcy17uz1-UAlk,3685
6
- syft_flwr/flower_server.py,sha256=sJwSEqePmkmWKGFXm2E44Ugoc6aaz-6tM7UaeWM2-co,1353
7
- syft_flwr/flwr_compatibility.py,sha256=vURf9rfsZ1uPm04szw6RpGYxtlG3BE4tW3YijptiGyk,3197
8
- syft_flwr/grid.py,sha256=Me2tivW0v1ApTjdjQffUc9f1UCHh1LtkYcKUjE82iZ8,7735
9
- syft_flwr/mounts.py,sha256=ry3_3eD4aPkRahk9eibfu88TpQjgp_KQ96G7yj692x4,2319
10
- syft_flwr/run.py,sha256=OPW9bVt366DT-U-SxMpMLNXASwTZjp7XNNXfDP767f4,2153
11
- syft_flwr/run_simulation.py,sha256=t3shhfzAWDUlf6iQmwf5sS9APZQE9mkaZ9MLCYs9Ng0,6922
12
- syft_flwr/serde.py,sha256=5fCI-cRUOh5wE7cXQd4J6jex1grRGnyD1Jx-VlEDOXM,495
13
- syft_flwr/utils.py,sha256=3dDYEB7btq9hxZ9UsfQWh3i44OerAhGXc5XaX5wO3-o,955
14
- syft_flwr/strategy/__init__.py,sha256=mpUmExjjFkqU8gg41XsOBKfO3aqCBe7XPJSU-_P7smU,97
15
- syft_flwr/strategy/fedavg.py,sha256=N8jULUkjvuaBIEVINowyQln8W8yFhkO-J8k0-iPcGMA,1562
16
- syft_flwr/templates/main.py.tpl,sha256=p0uK97jvLGk3LJdy1_HF1R5BQgIjaTGkYnr-csfh39M,791
17
- syft_flwr-0.1.7.dist-info/METADATA,sha256=-FVHhD66zCCZ3FUZDGwzISjNFJuKhzWp3gHlKKRDqb4,1255
18
- syft_flwr-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- syft_flwr-0.1.7.dist-info/entry_points.txt,sha256=o7oT0dCoHn-3WyIwdDw1lBh2q-GvhB_8s0hWeJU4myc,49
20
- syft_flwr-0.1.7.dist-info/licenses/LICENSE,sha256=0msOUar8uPZTqkAOTBp4rCzd7Jl9eRhfKiNufwrsg7k,11361
21
- syft_flwr-0.1.7.dist-info/RECORD,,