MEDfl 2.0.5.dev2__py3-none-any.whl → 2.0.5.dev4__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.
- MEDfl/rw/client.py +25 -25
- MEDfl/rw/model.py +25 -25
- MEDfl/rw/server.py +9 -10
- MEDfl/rw/strategy.py +33 -38
- {medfl-2.0.5.dev2.dist-info → medfl-2.0.5.dev4.dist-info}/METADATA +1 -1
- {medfl-2.0.5.dev2.dist-info → medfl-2.0.5.dev4.dist-info}/RECORD +9 -9
- {medfl-2.0.5.dev2.dist-info → medfl-2.0.5.dev4.dist-info}/WHEEL +0 -0
- {medfl-2.0.5.dev2.dist-info → medfl-2.0.5.dev4.dist-info}/licenses/LICENSE +0 -0
- {medfl-2.0.5.dev2.dist-info → medfl-2.0.5.dev4.dist-info}/top_level.txt +0 -0
MEDfl/rw/client.py
CHANGED
|
@@ -35,10 +35,10 @@ class DPConfig:
|
|
|
35
35
|
|
|
36
36
|
def __init__(
|
|
37
37
|
self,
|
|
38
|
-
noise_multiplier
|
|
39
|
-
max_grad_norm
|
|
40
|
-
batch_size
|
|
41
|
-
secure_rng
|
|
38
|
+
noise_multiplier=1.0,
|
|
39
|
+
max_grad_norm=1.0,
|
|
40
|
+
batch_size=32,
|
|
41
|
+
secure_rng=False,
|
|
42
42
|
):
|
|
43
43
|
self.noise_multiplier = noise_multiplier
|
|
44
44
|
self.max_grad_norm = max_grad_norm
|
|
@@ -49,15 +49,15 @@ class DPConfig:
|
|
|
49
49
|
class FlowerClient(fl.client.NumPyClient):
|
|
50
50
|
def __init__(
|
|
51
51
|
self,
|
|
52
|
-
server_address
|
|
53
|
-
data_path
|
|
54
|
-
dp_config
|
|
52
|
+
server_address,
|
|
53
|
+
data_path="data/data.csv",
|
|
54
|
+
dp_config=None,
|
|
55
55
|
# NEW (optional client overrides; do NOT remove old args)
|
|
56
|
-
val_frac
|
|
57
|
-
test_frac
|
|
58
|
-
id_col
|
|
59
|
-
test_ids
|
|
60
|
-
seed
|
|
56
|
+
val_frac=None,
|
|
57
|
+
test_frac=None,
|
|
58
|
+
id_col=None,
|
|
59
|
+
test_ids=None,
|
|
60
|
+
seed=42,
|
|
61
61
|
):
|
|
62
62
|
self.server_address = server_address
|
|
63
63
|
self.dp_config = dp_config
|
|
@@ -83,31 +83,31 @@ class FlowerClient(fl.client.NumPyClient):
|
|
|
83
83
|
self.y_tensor = torch.tensor(y_default, dtype=torch.float32)
|
|
84
84
|
|
|
85
85
|
# Placeholders; we lazily build loaders/model on the first fit when we see server config
|
|
86
|
-
self.train_loader
|
|
87
|
-
self.val_loader
|
|
88
|
-
self.test_loader
|
|
86
|
+
self.train_loader = None
|
|
87
|
+
self.val_loader = None
|
|
88
|
+
self.test_loader = None
|
|
89
89
|
|
|
90
|
-
self.model
|
|
90
|
+
self.model = None
|
|
91
91
|
self.criterion = nn.BCEWithLogitsLoss()
|
|
92
|
-
self.optimizer
|
|
92
|
+
self.optimizer = None
|
|
93
93
|
|
|
94
94
|
# Effective settings (filled at first fit)
|
|
95
|
-
self.effective_features
|
|
96
|
-
self.effective_target
|
|
97
|
-
self.effective_val_frac
|
|
98
|
-
self.effective_test_frac
|
|
95
|
+
self.effective_features = self.feature_names[:]
|
|
96
|
+
self.effective_target = self.target_name
|
|
97
|
+
self.effective_val_frac = float(self.client_val_frac) if self.client_val_frac is not None else 0.0
|
|
98
|
+
self.effective_test_frac = float(self.client_test_frac) if self.client_test_frac is not None else 0.0
|
|
99
99
|
|
|
100
100
|
self._initialized = False
|
|
101
101
|
self._dp_attached = False # to avoid wrapping twice
|
|
102
102
|
|
|
103
103
|
# ---------- helpers
|
|
104
104
|
|
|
105
|
-
def _mk_loader(self, X
|
|
105
|
+
def _mk_loader(self, X, y, batch_size, shuffle):
|
|
106
106
|
x_t = torch.tensor(X, dtype=torch.float32)
|
|
107
107
|
y_t = torch.tensor(y, dtype=torch.float32)
|
|
108
108
|
return DataLoader(TensorDataset(x_t, y_t), batch_size=batch_size, shuffle=shuffle)
|
|
109
109
|
|
|
110
|
-
def _lazy_init_from_server_config(self, config
|
|
110
|
+
def _lazy_init_from_server_config(self, config):
|
|
111
111
|
"""
|
|
112
112
|
Build model and (train, val, test) loaders once, using:
|
|
113
113
|
- Server-enforced schema: config['features'] (comma-separated), config['target']
|
|
@@ -226,7 +226,7 @@ class FlowerClient(fl.client.NumPyClient):
|
|
|
226
226
|
# ---------- build loaders
|
|
227
227
|
batch_size = self.dp_config.batch_size if self.dp_config else 32
|
|
228
228
|
self.train_loader = self._mk_loader(X_train, y_train, batch_size, shuffle=True)
|
|
229
|
-
self.val_loader = self._mk_loader(X_val, y_val, batch_size=
|
|
229
|
+
self.val_loader = self._mk_loader(X_val, y_val, batch_size=batch_size, shuffle=False) if len(y_val) else None
|
|
230
230
|
self.test_loader = self._mk_loader(X_test, y_test, batch_size, shuffle=False)
|
|
231
231
|
|
|
232
232
|
# ---------- model/optimizer
|
|
@@ -414,7 +414,7 @@ class FlowerClient(fl.client.NumPyClient):
|
|
|
414
414
|
"gpu_count": gpu_count,
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
def start(self)
|
|
417
|
+
def start(self):
|
|
418
418
|
fl.client.start_numpy_client(
|
|
419
419
|
server_address=self.server_address,
|
|
420
420
|
client=self,
|
MEDfl/rw/model.py
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import math
|
|
2
|
-
from typing import List, Optional, Literal, Callable, Tuple
|
|
3
2
|
import torch
|
|
4
3
|
import torch.nn as nn
|
|
5
4
|
import torch.nn.functional as F
|
|
6
5
|
|
|
7
|
-
Task = Literal["binary", "multiclass", "multilabel", "regression"]
|
|
8
|
-
|
|
9
6
|
_ACTS = {
|
|
10
7
|
"relu": nn.ReLU,
|
|
11
8
|
"gelu": nn.GELU,
|
|
@@ -16,7 +13,7 @@ _ACTS = {
|
|
|
16
13
|
"identity": nn.Identity,
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
def _make_activation(name
|
|
16
|
+
def _make_activation(name):
|
|
20
17
|
if name not in _ACTS:
|
|
21
18
|
raise ValueError(f"Unsupported activation '{name}'. Choose from {list(_ACTS)}")
|
|
22
19
|
return _ACTS[name]()
|
|
@@ -35,16 +32,16 @@ class Net(nn.Module):
|
|
|
35
32
|
|
|
36
33
|
def __init__(
|
|
37
34
|
self,
|
|
38
|
-
input_dim
|
|
39
|
-
hidden_dims
|
|
40
|
-
activation
|
|
41
|
-
batchnorm
|
|
42
|
-
dropout
|
|
43
|
-
task
|
|
44
|
-
num_classes
|
|
45
|
-
output_bias
|
|
46
|
-
return_logits
|
|
47
|
-
weight_init
|
|
35
|
+
input_dim,
|
|
36
|
+
hidden_dims=[64, 32],
|
|
37
|
+
activation="relu",
|
|
38
|
+
batchnorm=True,
|
|
39
|
+
dropout=0.0,
|
|
40
|
+
task="binary",
|
|
41
|
+
num_classes=None,
|
|
42
|
+
output_bias=True,
|
|
43
|
+
return_logits=True,
|
|
44
|
+
weight_init="kaiming",
|
|
48
45
|
):
|
|
49
46
|
super().__init__()
|
|
50
47
|
self.task = task
|
|
@@ -78,10 +75,9 @@ class Net(nn.Module):
|
|
|
78
75
|
else:
|
|
79
76
|
raise ValueError("dropout must be float|list[float]|None")
|
|
80
77
|
|
|
81
|
-
layers = []
|
|
82
|
-
in_dim = input_dim
|
|
83
78
|
self.blocks = nn.ModuleList()
|
|
84
|
-
|
|
79
|
+
in_dim = input_dim
|
|
80
|
+
for h, p in zip(hidden_dims, dropout):
|
|
85
81
|
block = nn.ModuleDict({
|
|
86
82
|
"lin": nn.Linear(in_dim, h, bias=True),
|
|
87
83
|
"bn": nn.BatchNorm1d(h) if batchnorm else nn.Identity(),
|
|
@@ -98,11 +94,15 @@ class Net(nn.Module):
|
|
|
98
94
|
self.apply(lambda m: self._init_weights(m, scheme=weight_init, activation=activation))
|
|
99
95
|
|
|
100
96
|
@staticmethod
|
|
101
|
-
def _init_weights(m
|
|
97
|
+
def _init_weights(m, scheme, activation):
|
|
102
98
|
if isinstance(m, nn.Linear):
|
|
103
99
|
if scheme == "kaiming":
|
|
104
100
|
nonlinearity = "leaky_relu" if activation == "leaky_relu" else "relu"
|
|
105
|
-
nn.init.kaiming_uniform_(
|
|
101
|
+
nn.init.kaiming_uniform_(
|
|
102
|
+
m.weight,
|
|
103
|
+
a=math.sqrt(5) if activation == "leaky_relu" else 0,
|
|
104
|
+
nonlinearity=nonlinearity,
|
|
105
|
+
)
|
|
106
106
|
elif scheme == "xavier":
|
|
107
107
|
nn.init.xavier_uniform_(m.weight)
|
|
108
108
|
if m.bias is not None:
|
|
@@ -110,7 +110,7 @@ class Net(nn.Module):
|
|
|
110
110
|
bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
|
|
111
111
|
nn.init.uniform_(m.bias, -bound, bound)
|
|
112
112
|
|
|
113
|
-
def forward(self, x
|
|
113
|
+
def forward(self, x):
|
|
114
114
|
for blk in self.blocks:
|
|
115
115
|
x = blk["lin"](x)
|
|
116
116
|
x = blk["bn"](x)
|
|
@@ -120,7 +120,7 @@ class Net(nn.Module):
|
|
|
120
120
|
return logits if self.return_logits else self._apply_output_activation(logits)
|
|
121
121
|
|
|
122
122
|
# Inference helpers
|
|
123
|
-
def _apply_output_activation(self, logits
|
|
123
|
+
def _apply_output_activation(self, logits):
|
|
124
124
|
if self.task == "binary":
|
|
125
125
|
return torch.sigmoid(logits) # (B, 1)
|
|
126
126
|
elif self.task == "multiclass":
|
|
@@ -128,14 +128,15 @@ class Net(nn.Module):
|
|
|
128
128
|
elif self.task == "multilabel":
|
|
129
129
|
return torch.sigmoid(logits) # (B, L)
|
|
130
130
|
elif self.task == "regression":
|
|
131
|
-
return logits
|
|
131
|
+
return logits # raw regression output
|
|
132
132
|
else:
|
|
133
133
|
raise RuntimeError("Invalid task")
|
|
134
134
|
|
|
135
135
|
@torch.no_grad()
|
|
136
|
-
def predict(self, x
|
|
137
|
-
out = self.forward(x)
|
|
136
|
+
def predict(self, x):
|
|
137
|
+
out = self.forward(x)
|
|
138
138
|
probs = self._apply_output_activation(out) if self.return_logits else out
|
|
139
|
+
|
|
139
140
|
if self.task == "binary":
|
|
140
141
|
return (probs >= 0.5).long()
|
|
141
142
|
elif self.task == "multiclass":
|
|
@@ -144,4 +145,3 @@ class Net(nn.Module):
|
|
|
144
145
|
return (probs >= 0.5).long()
|
|
145
146
|
elif self.task == "regression":
|
|
146
147
|
return probs
|
|
147
|
-
|
MEDfl/rw/server.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import flwr as fl
|
|
2
2
|
from flwr.server.strategy import FedAvg
|
|
3
3
|
from flwr.server.server import ServerConfig
|
|
4
|
-
from typing import Optional, Any
|
|
5
4
|
from MEDfl.rw.strategy import Strategy
|
|
6
5
|
import asyncio
|
|
7
6
|
from flwr.server.client_manager import ClientManager
|
|
@@ -29,11 +28,11 @@ class FederatedServer:
|
|
|
29
28
|
|
|
30
29
|
def __init__(
|
|
31
30
|
self,
|
|
32
|
-
host
|
|
33
|
-
port
|
|
34
|
-
num_rounds
|
|
35
|
-
strategy
|
|
36
|
-
certificates
|
|
31
|
+
host="0.0.0.0",
|
|
32
|
+
port=8080,
|
|
33
|
+
num_rounds=3,
|
|
34
|
+
strategy=None,
|
|
35
|
+
certificates=None,
|
|
37
36
|
):
|
|
38
37
|
"""
|
|
39
38
|
Initialize the FederatedServer.
|
|
@@ -60,7 +59,7 @@ class FederatedServer:
|
|
|
60
59
|
self.connected_clients = [] # Track connected client IDs
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
def start(self)
|
|
62
|
+
def start(self):
|
|
64
63
|
"""
|
|
65
64
|
Start the Flower server with the configured strategy and track client connections.
|
|
66
65
|
"""
|
|
@@ -89,7 +88,7 @@ class TrackingClientManager(fl.server.client_manager.SimpleClientManager):
|
|
|
89
88
|
client_properties (dict): Placeholder for storing client-specific properties.
|
|
90
89
|
"""
|
|
91
90
|
|
|
92
|
-
def __init__(self, server
|
|
91
|
+
def __init__(self, server):
|
|
93
92
|
"""
|
|
94
93
|
Initialize the TrackingClientManager.
|
|
95
94
|
|
|
@@ -100,7 +99,7 @@ class TrackingClientManager(fl.server.client_manager.SimpleClientManager):
|
|
|
100
99
|
self.server = server
|
|
101
100
|
self.client_properties = {}
|
|
102
101
|
|
|
103
|
-
def register(self, client
|
|
102
|
+
def register(self, client):
|
|
104
103
|
"""
|
|
105
104
|
Register a client and log its connection.
|
|
106
105
|
|
|
@@ -116,7 +115,7 @@ class TrackingClientManager(fl.server.client_manager.SimpleClientManager):
|
|
|
116
115
|
asyncio.run(self._fetch_and_log_hostname(client))
|
|
117
116
|
return success
|
|
118
117
|
|
|
119
|
-
async def _fetch_and_log_hostname(self, client
|
|
118
|
+
async def _fetch_and_log_hostname(self, client):
|
|
120
119
|
"""
|
|
121
120
|
Asynchronously fetch and log the client's hostname or CID.
|
|
122
121
|
|
MEDfl/rw/strategy.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import numpy as np
|
|
5
5
|
import flwr as fl
|
|
6
|
-
from typing import Callable, Optional, Dict, Any, List, Tuple
|
|
7
6
|
from flwr.common import GetPropertiesIns
|
|
8
7
|
from flwr.server.client_manager import ClientManager
|
|
9
8
|
from flwr.server.client_proxy import ClientProxy
|
|
@@ -14,18 +13,14 @@ import torch
|
|
|
14
13
|
# ===================================================
|
|
15
14
|
# Custom metric aggregation functions
|
|
16
15
|
# ===================================================
|
|
17
|
-
def aggregate_fit_metrics(
|
|
18
|
-
results: List[Tuple[int, Dict[str, float]]]
|
|
19
|
-
) -> Dict[str, float]:
|
|
16
|
+
def aggregate_fit_metrics(results):
|
|
20
17
|
total = sum(n for n, _ in results)
|
|
21
18
|
loss = sum(m.get("train_loss", 0.0) * n for n, m in results) / total
|
|
22
19
|
acc = sum(m.get("train_accuracy", 0.0) * n for n, m in results) / total
|
|
23
20
|
auc = sum(m.get("train_auc", 0.0) * n for n, m in results) / total
|
|
24
21
|
return {"train_loss": loss, "train_accuracy": acc, "train_auc": auc}
|
|
25
22
|
|
|
26
|
-
def aggregate_eval_metrics(
|
|
27
|
-
results: List[Tuple[int, Dict[str, float]]]
|
|
28
|
-
) -> Dict[str, float]:
|
|
23
|
+
def aggregate_eval_metrics(results):
|
|
29
24
|
total = sum(n for n, _ in results)
|
|
30
25
|
loss = sum(m.get("eval_loss", 0.0) * n for n, m in results) / total
|
|
31
26
|
acc = sum(m.get("eval_accuracy", 0.0) * n for n, m in results) / total
|
|
@@ -71,33 +66,33 @@ class Strategy:
|
|
|
71
66
|
|
|
72
67
|
def __init__(
|
|
73
68
|
self,
|
|
74
|
-
name
|
|
75
|
-
fraction_fit
|
|
76
|
-
fraction_evaluate
|
|
77
|
-
min_fit_clients
|
|
78
|
-
min_evaluate_clients
|
|
79
|
-
min_available_clients
|
|
80
|
-
initial_parameters
|
|
81
|
-
evaluate_fn
|
|
82
|
-
fit_metrics_aggregation_fn
|
|
83
|
-
evaluate_metrics_aggregation_fn
|
|
84
|
-
local_epochs
|
|
85
|
-
threshold
|
|
86
|
-
learning_rate
|
|
87
|
-
optimizer_name
|
|
88
|
-
savingPath
|
|
89
|
-
saveOnRounds
|
|
90
|
-
total_rounds
|
|
91
|
-
features
|
|
92
|
-
target
|
|
93
|
-
val_fraction
|
|
94
|
-
test_fraction
|
|
69
|
+
name="FedAvg",
|
|
70
|
+
fraction_fit=1.0,
|
|
71
|
+
fraction_evaluate=1.0,
|
|
72
|
+
min_fit_clients=2,
|
|
73
|
+
min_evaluate_clients=2,
|
|
74
|
+
min_available_clients=2,
|
|
75
|
+
initial_parameters=None,
|
|
76
|
+
evaluate_fn=None,
|
|
77
|
+
fit_metrics_aggregation_fn=None,
|
|
78
|
+
evaluate_metrics_aggregation_fn=None,
|
|
79
|
+
local_epochs=1,
|
|
80
|
+
threshold=0.5,
|
|
81
|
+
learning_rate=0.01,
|
|
82
|
+
optimizer_name="SGD",
|
|
83
|
+
savingPath="",
|
|
84
|
+
saveOnRounds=3,
|
|
85
|
+
total_rounds=3,
|
|
86
|
+
features="",
|
|
87
|
+
target="",
|
|
88
|
+
val_fraction=0.10,
|
|
89
|
+
test_fraction=0.10,
|
|
95
90
|
# NEW: splitting control (added at the end to not break existing calls)
|
|
96
|
-
split_mode
|
|
97
|
-
client_fractions
|
|
91
|
+
split_mode="global", # "global" or "per_client"
|
|
92
|
+
client_fractions=None,
|
|
98
93
|
# NEW: id column for test_ids mapping
|
|
99
|
-
id_col
|
|
100
|
-
)
|
|
94
|
+
id_col="id",
|
|
95
|
+
):
|
|
101
96
|
self.name = name
|
|
102
97
|
self.fraction_fit = fraction_fit
|
|
103
98
|
self.fraction_evaluate = fraction_evaluate
|
|
@@ -128,14 +123,14 @@ class Strategy:
|
|
|
128
123
|
self.client_fractions = client_fractions or {}
|
|
129
124
|
self.id_col = id_col
|
|
130
125
|
|
|
131
|
-
self.strategy_object
|
|
126
|
+
self.strategy_object = None
|
|
132
127
|
|
|
133
|
-
def create_strategy(self)
|
|
128
|
+
def create_strategy(self):
|
|
134
129
|
# 1) Pick the Flower Strategy class
|
|
135
130
|
StrategyClass = getattr(fl.server.strategy, self.name)
|
|
136
131
|
|
|
137
132
|
# 2) Define on_fit_config_fn _before_ instantiation (global defaults)
|
|
138
|
-
def fit_config_fn(server_round
|
|
133
|
+
def fit_config_fn(server_round):
|
|
139
134
|
return {
|
|
140
135
|
"local_epochs": self.local_epochs,
|
|
141
136
|
"threshold": self.threshold,
|
|
@@ -150,7 +145,7 @@ class Strategy:
|
|
|
150
145
|
}
|
|
151
146
|
|
|
152
147
|
# 3) Build params including on_fit_config_fn
|
|
153
|
-
params
|
|
148
|
+
params = {
|
|
154
149
|
"fraction_fit": self.fraction_fit,
|
|
155
150
|
"fraction_evaluate": self.fraction_evaluate,
|
|
156
151
|
"min_fit_clients": self.min_fit_clients,
|
|
@@ -226,9 +221,9 @@ class Strategy:
|
|
|
226
221
|
original_conf_fit = strat.configure_fit
|
|
227
222
|
|
|
228
223
|
def wrapped_conf_fit(
|
|
229
|
-
server_round
|
|
224
|
+
server_round,
|
|
230
225
|
parameters,
|
|
231
|
-
client_manager
|
|
226
|
+
client_manager
|
|
232
227
|
):
|
|
233
228
|
selected = original_conf_fit(
|
|
234
229
|
server_round=server_round,
|
|
@@ -20,17 +20,17 @@ MEDfl/NetManager/net_manager_queries.py,sha256=j-CLQPjtTLyZuFPhIcwJStD7L7xtZpkmk
|
|
|
20
20
|
MEDfl/NetManager/network.py,sha256=5t705fzWc-BRg-QPAbAcDv5ckDGzsPwj_Q5V0iTgkx0,6829
|
|
21
21
|
MEDfl/NetManager/node.py,sha256=t90QuYZ8M1X_AG1bwTta0CnlOuodqkmpVda2K7NOgHc,6542
|
|
22
22
|
MEDfl/rw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
MEDfl/rw/client.py,sha256=
|
|
24
|
-
MEDfl/rw/model.py,sha256
|
|
25
|
-
MEDfl/rw/server.py,sha256=
|
|
26
|
-
MEDfl/rw/strategy.py,sha256=
|
|
23
|
+
MEDfl/rw/client.py,sha256=iYA6GkCQ7YQ9B60OrkuDnHCFnMpE8fPdafiyBMknOP0,17493
|
|
24
|
+
MEDfl/rw/model.py,sha256=IvC5a1ADyn7mKeSBq1Lk_UnkSJOCErPCmpEPSOFxHpk,5343
|
|
25
|
+
MEDfl/rw/server.py,sha256=JBNSSX8tihrLIjgDTrGfmsXXOq_00gtS0s9qCgbqrHY,4834
|
|
26
|
+
MEDfl/rw/strategy.py,sha256=uQvd9lS_PZc234ghBKvrhwdc7ooztHzewuvwpWzBsJ8,13206
|
|
27
27
|
MEDfl/scripts/__init__.py,sha256=Pq1weevsPaU7MRMHfBYeyT0EOFeWLeVM6Y1DVz6jw1A,48
|
|
28
28
|
MEDfl/scripts/base.py,sha256=QrmG7gkiPYkAy-5tXxJgJmOSLGAKeIVH6i0jq7G9xnA,752
|
|
29
29
|
MEDfl/scripts/create_db.py,sha256=MnFtZkTueRZ-3qXPNX4JsXjOKj-4mlkxoRhSFdRcvJw,3817
|
|
30
30
|
alembic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
alembic/env.py,sha256=-aSZ6SlJeK1ZeqHgM-54hOi9LhJRFP0SZGjut-JnY-4,1588
|
|
32
|
-
medfl-2.0.5.
|
|
33
|
-
medfl-2.0.5.
|
|
34
|
-
medfl-2.0.5.
|
|
35
|
-
medfl-2.0.5.
|
|
36
|
-
medfl-2.0.5.
|
|
32
|
+
medfl-2.0.5.dev4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
33
|
+
medfl-2.0.5.dev4.dist-info/METADATA,sha256=XXERejCSaSWf9BYuGuG24uc1hakKPuDC12ctD_Ox9Co,6914
|
|
34
|
+
medfl-2.0.5.dev4.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
|
35
|
+
medfl-2.0.5.dev4.dist-info/top_level.txt,sha256=dIL9X8HOFuaVSzpg40DVveDPrymWRoShHtspH7kkjdI,14
|
|
36
|
+
medfl-2.0.5.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|