workbench 0.8.184__py3-none-any.whl → 0.8.185__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 workbench might be problematic. Click here for more details.
- workbench/core/artifacts/endpoint_core.py +8 -5
- workbench/core/transforms/pandas_transforms/pandas_to_features.py +27 -0
- workbench/model_scripts/uq_models/generated_model_script.py +1 -1
- workbench/utils/xgboost_model_utils.py +100 -0
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/METADATA +1 -1
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/RECORD +10 -10
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/WHEEL +0 -0
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/entry_points.txt +0 -0
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/licenses/LICENSE +0 -0
- {workbench-0.8.184.dist-info → workbench-0.8.185.dist-info}/top_level.txt +0 -0
|
@@ -475,17 +475,20 @@ class EndpointCore(Artifact):
|
|
|
475
475
|
training_df = fs.view("training").pull_dataframe()
|
|
476
476
|
|
|
477
477
|
# Run inference on the endpoint to get UQ outputs
|
|
478
|
-
|
|
478
|
+
uq_df = self.inference(training_df)
|
|
479
479
|
|
|
480
480
|
# Identify UQ-specific columns (quantiles and prediction_std)
|
|
481
|
-
uq_columns = [col for col in
|
|
481
|
+
uq_columns = [col for col in uq_df.columns if col.startswith("q_") or col == "prediction_std"]
|
|
482
482
|
|
|
483
483
|
# Merge UQ columns with out-of-fold predictions
|
|
484
484
|
if uq_columns:
|
|
485
|
-
# Keep id_column and UQ columns, drop 'prediction' to avoid conflict
|
|
486
|
-
|
|
487
|
-
uq_df = full_inference_df[merge_columns]
|
|
485
|
+
# Keep id_column and UQ columns, drop 'prediction' to avoid conflict when merging
|
|
486
|
+
uq_df = uq_df[[id_column] + uq_columns]
|
|
488
487
|
|
|
488
|
+
# Drop duplicates in uq_df based on id_column
|
|
489
|
+
uq_df = uq_df.drop_duplicates(subset=[id_column])
|
|
490
|
+
|
|
491
|
+
# Merge UQ columns into out_of_fold_df
|
|
489
492
|
out_of_fold_df = pd.merge(out_of_fold_df, uq_df, on=id_column, how="left")
|
|
490
493
|
additional_columns = uq_columns
|
|
491
494
|
self.log.info(f"Added UQ columns: {', '.join(additional_columns)}")
|
|
@@ -327,9 +327,36 @@ class PandasToFeatures(Transform):
|
|
|
327
327
|
self.delete_existing()
|
|
328
328
|
self.output_feature_group = self.create_feature_group()
|
|
329
329
|
|
|
330
|
+
def mac_spawn_hack(self):
|
|
331
|
+
"""Workaround for macOS Tahoe fork/spawn issue with SageMaker FeatureStore ingest.
|
|
332
|
+
|
|
333
|
+
See: https://github.com/aws/sagemaker-python-sdk/issues/5312
|
|
334
|
+
macOS Tahoe 26+ has issues with forked processes creating boto3 sessions.
|
|
335
|
+
This forces spawn mode on macOS to avoid the hang.
|
|
336
|
+
"""
|
|
337
|
+
import platform
|
|
338
|
+
|
|
339
|
+
if platform.system() == "Darwin": # macOS
|
|
340
|
+
self.log.warning("macOS detected, forcing 'spawn' mode for multiprocessing (Tahoe hang workaround)")
|
|
341
|
+
import multiprocessing
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
import multiprocess
|
|
345
|
+
|
|
346
|
+
multiprocess.set_start_method("spawn", force=True)
|
|
347
|
+
except (RuntimeError, ImportError):
|
|
348
|
+
pass # Already set or multiprocess not available
|
|
349
|
+
try:
|
|
350
|
+
multiprocessing.set_start_method("spawn", force=True)
|
|
351
|
+
except RuntimeError:
|
|
352
|
+
pass # Already set
|
|
353
|
+
|
|
330
354
|
def transform_impl(self):
|
|
331
355
|
"""Transform Implementation: Ingest the data into the Feature Group"""
|
|
332
356
|
|
|
357
|
+
# Workaround for macOS Tahoe hang issue
|
|
358
|
+
self.mac_spawn_hack()
|
|
359
|
+
|
|
333
360
|
# Now we actually push the data into the Feature Group (called ingestion)
|
|
334
361
|
self.log.important(f"Ingesting rows into Feature Group {self.output_name}...")
|
|
335
362
|
ingest_manager = self.output_feature_group.ingest(self.output_df, max_workers=8, max_processes=4, wait=False)
|
|
@@ -19,7 +19,7 @@ from typing import List, Tuple
|
|
|
19
19
|
# Template Placeholders
|
|
20
20
|
TEMPLATE_PARAMS = {
|
|
21
21
|
"target": "udm_asy_res_free_percent",
|
|
22
|
-
"features": ['naromatom', '
|
|
22
|
+
"features": ['naromatom', 'minabspartialcharge', 'bcut2d_mrhi', 'smr_vsa10', 'vsa_estate2', 'minpartialcharge', 'xpc_5d', 'sps', 'xc_3dv', 'smr_vsa7', 'bcut2d_logplow', 'mollogp', 'vsa_estate1', 'num_s_centers', 'vsa_estate4', 'peoe_vsa13', 'fr_nh2', 'bertzct', 'estate_vsa4', 'vsa_estate9', 'smr_vsa3', 'fr_nh1', 'molwt', 'estate_vsa5', 'slogp_vsa5', 'maxpartialcharge', 'estate_vsa1', 'fr_hoccn', 'xc_5d', 'nbase', 'chi1v', 'peoe_vsa10', 'tpsa', 'vsa_estate3', 'chi2v', 'estate_vsa8', 'numheteroatoms', 'estate_vsa2', 'peoe_vsa1', 'labuteasa', 'axp_4d', 'xch_7dv', 'chi0n', 'num_r_centers', 'vsa_estate8', 'minabsestateindex', 'bcut2d_chglo', 'bcut2d_mwhi', 'fr_nh0', 'chi4n', 'estate_vsa9', 'smr_vsa5', 'peoe_vsa2', 'peoe_vsa7', 'peoe_vsa9', 'kappa3', 'slogp_vsa3', 'fr_arn', 'estate_vsa3', 'avgipc', 'axp_5d', 'xpc_6d', 'c2sp2', 'peoe_vsa5', 'vsa_estate5', 'balabanj', 'maxabspartialcharge', 'fr_aniline', 'fr_piperdine', 'vsa_estate6', 'bcut2d_mwlow', 'numsaturatedheterocycles', 'vsa_estate10', 'smr_vsa1', 'estate_vsa6', 'smr_vsa6', 'fpdensitymorgan1', 'peoe_vsa3', 'peoe_vsa8', 'smr_vsa9', 'slogp_vsa2', 'nocount', 'fpdensitymorgan3', 'axp_6d', 'bcut2d_mrlow', 'bcut2d_logphi', 'axp_4dv', 'fpdensitymorgan2', 'mp', 'xp_5d', 'fr_nhpyrrole', 'mz', 'mv', 'vsa_estate7', 'axp_7dv', 'mi', 'c1sp2', 'xpc_6dv', 'slogp_vsa10', 'xp_7d', 'axp_3dv', 'peoe_vsa4', 'peoe_vsa6', 'axp_2dv', 'xch_5dv', 'qed', 'estate_vsa7', 'numaromaticrings', 'chi1n', 'axp_0d', 'axp_6dv', 'numrotatablebonds', 'hallkieralpha', 'c1sp3', 'xc_4dv', 'kappa2', 'bcut2d_chghi', 'xch_7d', 'axp_0dv', 'slogp_vsa7', 'axp_7d', 'minestateindex', 'axp_2d', 'axp_1d', 'chi0', 'fractioncsp3', 'slogp_vsa6', 'axp_1dv', 'chi2n', 'xp_6dv', 'maxestateindex', 'xpc_4d', 'numaliphaticheterocycles', 'chi1', 'phi', 'chi3n', 'xc_4d', 'xc_3d', 'peoe_vsa12', 'xp_6d', 'chi3v', 'axp_3d', 'axp_5dv', 'fr_benzene', 'slogp_vsa4', 'fr_pyridine', 'fr_aryl_methyl', 'xp_5dv', 'c3sp3', 'xp_7dv', 'slogp_vsa1', 'peoe_vsa11', 'mse', 'xc_5dv', 'xpc_5dv', 'xc_6dv', 'xp_0dv', 'xch_5d', 'c3sp2', 'numatomstereocenters', 'numhacceptors', 'fr_imidazole', 'numsaturatedrings', 'xpc_4dv', 'chi0v', 'numheterocycles', 'xch_6dv', 'estate_vsa10', 'chi4v', 'mare', 'numhdonors', 'xch_6d', 'xp_4d', 'fr_ar_n', 'numunspecifiedatomstereocenters', 'numspiroatoms', 'xch_4dv', 'fr_morpholine', 'fr_methoxy', 'mm', 'fr_piperzine'],
|
|
23
23
|
"compressed_features": [],
|
|
24
24
|
"train_all_data": True,
|
|
25
25
|
"hyperparameters": {},
|
|
@@ -386,6 +386,106 @@ def cross_fold_inference(workbench_model: Any, nfolds: int = 5) -> Tuple[Dict[st
|
|
|
386
386
|
return metrics_dict, predictions_df
|
|
387
387
|
|
|
388
388
|
|
|
389
|
+
def leave_one_out_inference(workbench_model: Any) -> pd.DataFrame:
|
|
390
|
+
"""
|
|
391
|
+
Performs leave-one-out cross-validation (parallelized).
|
|
392
|
+
For datasets > 1000 rows, first identifies top 100 worst predictions via 10-fold CV,
|
|
393
|
+
then performs true leave-one-out on those 100 samples.
|
|
394
|
+
Each model trains on ALL data except one sample.
|
|
395
|
+
"""
|
|
396
|
+
from workbench.api import FeatureSet
|
|
397
|
+
from joblib import Parallel, delayed
|
|
398
|
+
from tqdm import tqdm
|
|
399
|
+
|
|
400
|
+
def train_and_predict_one(model_params, is_classifier, X, y, train_idx, val_idx):
|
|
401
|
+
"""Train on train_idx, predict on val_idx."""
|
|
402
|
+
model = xgb.XGBClassifier(**model_params) if is_classifier else xgb.XGBRegressor(**model_params)
|
|
403
|
+
model.fit(X[train_idx], y[train_idx])
|
|
404
|
+
return model.predict(X[val_idx])[0]
|
|
405
|
+
|
|
406
|
+
# Load model and get params
|
|
407
|
+
model_artifact_uri = workbench_model.model_data_url()
|
|
408
|
+
loaded_model = xgboost_model_from_s3(model_artifact_uri)
|
|
409
|
+
if loaded_model is None:
|
|
410
|
+
log.error("No XGBoost model found in the artifact.")
|
|
411
|
+
return pd.DataFrame()
|
|
412
|
+
|
|
413
|
+
if isinstance(loaded_model, (xgb.XGBClassifier, xgb.XGBRegressor)):
|
|
414
|
+
is_classifier = isinstance(loaded_model, xgb.XGBClassifier)
|
|
415
|
+
model_params = loaded_model.get_params()
|
|
416
|
+
elif isinstance(loaded_model, xgb.Booster):
|
|
417
|
+
log.warning("Deprecated: Loaded model is a Booster, wrapping in sklearn model.")
|
|
418
|
+
is_classifier = workbench_model.model_type.value == "classifier"
|
|
419
|
+
model_params = {"enable_categorical": True}
|
|
420
|
+
else:
|
|
421
|
+
log.error(f"Unexpected model type: {type(loaded_model)}")
|
|
422
|
+
return pd.DataFrame()
|
|
423
|
+
|
|
424
|
+
# Load and prepare data
|
|
425
|
+
fs = FeatureSet(workbench_model.get_input())
|
|
426
|
+
df = fs.view("training").pull_dataframe()
|
|
427
|
+
id_col = fs.id_column
|
|
428
|
+
target_col = workbench_model.target()
|
|
429
|
+
feature_cols = workbench_model.features()
|
|
430
|
+
|
|
431
|
+
# Convert string features to categorical
|
|
432
|
+
for col in feature_cols:
|
|
433
|
+
if df[col].dtype in ["object", "string"]:
|
|
434
|
+
df[col] = df[col].astype("category")
|
|
435
|
+
|
|
436
|
+
# Determine which samples to run LOO on
|
|
437
|
+
if len(df) > 1000:
|
|
438
|
+
log.important(f"Dataset has {len(df)} rows. Running 10-fold CV to identify top 1000 worst predictions...")
|
|
439
|
+
_, predictions_df = cross_fold_inference(workbench_model, nfolds=10)
|
|
440
|
+
predictions_df["residual_abs"] = np.abs(predictions_df[target_col] - predictions_df["prediction"])
|
|
441
|
+
worst_samples = predictions_df.nlargest(1000, "residual_abs")
|
|
442
|
+
worst_ids = worst_samples[id_col].values
|
|
443
|
+
loo_indices = df[df[id_col].isin(worst_ids)].index.values
|
|
444
|
+
log.important(f"Running leave-one-out CV on 1000 worst samples. Each model trains on {len(df)-1} rows...")
|
|
445
|
+
else:
|
|
446
|
+
log.important(f"Running leave-one-out CV on all {len(df)} samples...")
|
|
447
|
+
loo_indices = df.index.values
|
|
448
|
+
|
|
449
|
+
# Prepare full dataset for training
|
|
450
|
+
X_full = df[feature_cols].values
|
|
451
|
+
y_full = df[target_col].values
|
|
452
|
+
|
|
453
|
+
# Encode target if classifier
|
|
454
|
+
label_encoder = LabelEncoder() if is_classifier else None
|
|
455
|
+
if label_encoder:
|
|
456
|
+
y_full = label_encoder.fit_transform(y_full)
|
|
457
|
+
|
|
458
|
+
# Generate LOO splits
|
|
459
|
+
splits = []
|
|
460
|
+
for loo_idx in loo_indices:
|
|
461
|
+
train_idx = np.delete(np.arange(len(X_full)), loo_idx)
|
|
462
|
+
val_idx = np.array([loo_idx])
|
|
463
|
+
splits.append((train_idx, val_idx))
|
|
464
|
+
|
|
465
|
+
# Parallel execution
|
|
466
|
+
predictions = Parallel(n_jobs=4)(
|
|
467
|
+
delayed(train_and_predict_one)(model_params, is_classifier, X_full, y_full, train_idx, val_idx)
|
|
468
|
+
for train_idx, val_idx in tqdm(splits, desc="LOO CV")
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# Build results dataframe
|
|
472
|
+
predictions_array = np.array(predictions)
|
|
473
|
+
if label_encoder:
|
|
474
|
+
predictions_array = label_encoder.inverse_transform(predictions_array.astype(int))
|
|
475
|
+
|
|
476
|
+
predictions_df = pd.DataFrame(
|
|
477
|
+
{
|
|
478
|
+
id_col: df.loc[loo_indices, id_col].values,
|
|
479
|
+
target_col: df.loc[loo_indices, target_col].values,
|
|
480
|
+
"prediction": predictions_array,
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
predictions_df["residual_abs"] = np.abs(predictions_df[target_col] - predictions_df["prediction"])
|
|
485
|
+
|
|
486
|
+
return predictions_df
|
|
487
|
+
|
|
488
|
+
|
|
389
489
|
if __name__ == "__main__":
|
|
390
490
|
"""Exercise the Model Utilities"""
|
|
391
491
|
from workbench.api import Model, FeatureSet
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workbench
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.185
|
|
4
4
|
Summary: Workbench: A Dashboard and Python API for creating and deploying AWS SageMaker Model Pipelines
|
|
5
5
|
Author-email: SuperCowPowers LLC <support@supercowpowers.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -54,7 +54,7 @@ workbench/core/artifacts/cached_artifact_mixin.py,sha256=ngqFLZ4cQx_TFouXZgXZQsv
|
|
|
54
54
|
workbench/core/artifacts/data_capture_core.py,sha256=q8f79rRTYiZ7T4IQRWXl8ZvPpcvZyNxYERwvo8o0OQc,14858
|
|
55
55
|
workbench/core/artifacts/data_source_abstract.py,sha256=5IRCzFVK-17cd4NXPMRfx99vQAmQ0WHE5jcm5RfsVTg,10619
|
|
56
56
|
workbench/core/artifacts/data_source_factory.py,sha256=YL_tA5fsgubbB3dPF6T4tO0rGgz-6oo3ge4i_YXVC-M,2380
|
|
57
|
-
workbench/core/artifacts/endpoint_core.py,sha256=
|
|
57
|
+
workbench/core/artifacts/endpoint_core.py,sha256=gQdOHtjEeC1WCHbKZNaDQ0eeOka1sCO-zcABDO4_Egk,51965
|
|
58
58
|
workbench/core/artifacts/feature_set_core.py,sha256=7b1o_PzxtwaYC-W2zxlkltiO0fYULA8CVGWwHNmqgtI,31457
|
|
59
59
|
workbench/core/artifacts/model_core.py,sha256=wjoa2GQnzrrTM-E2VgYZHT9Ixebl3LaKbJL0YvEdrJY,51546
|
|
60
60
|
workbench/core/artifacts/monitor_core.py,sha256=M307yz7tEzOEHgv-LmtVy9jKjSbM98fHW3ckmNYrwlU,27897
|
|
@@ -109,7 +109,7 @@ workbench/core/transforms/pandas_transforms/__init__.py,sha256=xL4MT8-fZ1SFqDbTL
|
|
|
109
109
|
workbench/core/transforms/pandas_transforms/data_to_pandas.py,sha256=sJHPeuNF8Q8aQqgRnkdWkyvur5cbggdUVIwR-xF3Dlo,3621
|
|
110
110
|
workbench/core/transforms/pandas_transforms/features_to_pandas.py,sha256=af6xdPt2V4zhh-SzQa_UYxdmNMzMLXbrbsznV5QoIJg,3441
|
|
111
111
|
workbench/core/transforms/pandas_transforms/pandas_to_data.py,sha256=cqo6hQmzUGUFACvNuVLZQdgrlXrQIu4NjqK-ujPmoIc,9181
|
|
112
|
-
workbench/core/transforms/pandas_transforms/pandas_to_features.py,sha256=
|
|
112
|
+
workbench/core/transforms/pandas_transforms/pandas_to_features.py,sha256=mj00L40PXhw-JHG2SZe53yJAzicgn4xuM2VbmOY-wsM,21480
|
|
113
113
|
workbench/core/transforms/pandas_transforms/pandas_to_features_chunked.py,sha256=0R8mQlWfbIlTVmYUmrtu2gsw0AE815k6kqPgpd0bmyQ,4422
|
|
114
114
|
workbench/core/views/__init__.py,sha256=UZJMAJBCMVM3uSYmnFg8c2LWtdu9-479WNAdVMIohAc,962
|
|
115
115
|
workbench/core/views/column_subset_view.py,sha256=vGDKTTGrPIY-IFOeWvudJrhKiq0OjWDp5rTuuj-X40U,4261
|
|
@@ -156,7 +156,7 @@ workbench/model_scripts/pytorch_model/requirements.txt,sha256=ICS5nW0wix44EJO2tJ
|
|
|
156
156
|
workbench/model_scripts/scikit_learn/generated_model_script.py,sha256=c73ZpJBlU5k13Nx-ZDkLXu7da40CYyhwjwwmuPq6uLg,12870
|
|
157
157
|
workbench/model_scripts/scikit_learn/requirements.txt,sha256=aVvwiJ3LgBUhM_PyFlb2gHXu_kpGPho3ANBzlOkfcvs,107
|
|
158
158
|
workbench/model_scripts/scikit_learn/scikit_learn.template,sha256=QQvqx-eX9ZTbYmyupq6R6vIQwosmsmY_MRBPaHyfjdk,12586
|
|
159
|
-
workbench/model_scripts/uq_models/generated_model_script.py,sha256=
|
|
159
|
+
workbench/model_scripts/uq_models/generated_model_script.py,sha256=gJb_5jBb4mcf41jb6578H9uYYH6Y-uFtPp1MNKXNFQQ,21231
|
|
160
160
|
workbench/model_scripts/uq_models/mapie.template,sha256=8VzoP-Wp3ECVIDqXVkiTS6bwmn3cd3dDZ2WjYPzXTi8,18955
|
|
161
161
|
workbench/model_scripts/uq_models/requirements.txt,sha256=fw7T7t_YJAXK3T6Ysbesxh_Agx_tv0oYx72cEBTqRDY,98
|
|
162
162
|
workbench/model_scripts/xgb_model/generated_model_script.py,sha256=Tbn7EMXxZZO8rDdKQ5fYCbpltACsMXNvuusLL9p-U5c,22319
|
|
@@ -242,7 +242,7 @@ workbench/utils/workbench_cache.py,sha256=IQchxB81iR4eVggHBxUJdXxUCRkqWz1jKe5gxN
|
|
|
242
242
|
workbench/utils/workbench_event_bridge.py,sha256=z1GmXOB-Qs7VOgC6Hjnp2DI9nSEWepaSXejACxTIR7o,4150
|
|
243
243
|
workbench/utils/workbench_logging.py,sha256=WCuMWhQwibrvcGAyj96h2wowh6dH7zNlDJ7sWUzdCeI,10263
|
|
244
244
|
workbench/utils/workbench_sqs.py,sha256=RwM80z7YWwdtMaCKh7KWF8v38f7eBRU7kyC7ZhTRuI0,2072
|
|
245
|
-
workbench/utils/xgboost_model_utils.py,sha256=
|
|
245
|
+
workbench/utils/xgboost_model_utils.py,sha256=rPfbl5sCZOttkilAHSmYU_u9JIDeYsGi2JrEs-zzcwI,21597
|
|
246
246
|
workbench/utils/chem_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
247
247
|
workbench/utils/chem_utils/fingerprints.py,sha256=Qvs8jaUwguWUq3Q3j695MY0t0Wk3BvroW-oWBwalMUo,5255
|
|
248
248
|
workbench/utils/chem_utils/misc.py,sha256=Nevf8_opu-uIPrv_1_0ubuFVVo2_fGUkMoLAHB3XAeo,7372
|
|
@@ -287,9 +287,9 @@ workbench/web_interface/page_views/main_page.py,sha256=X4-KyGTKLAdxR-Zk2niuLJB2Y
|
|
|
287
287
|
workbench/web_interface/page_views/models_page_view.py,sha256=M0bdC7bAzLyIaE2jviY12FF4abdMFZmg6sFuOY_LaGI,2650
|
|
288
288
|
workbench/web_interface/page_views/page_view.py,sha256=Gh6YnpOGlUejx-bHZAf5pzqoQ1H1R0OSwOpGhOBO06w,455
|
|
289
289
|
workbench/web_interface/page_views/pipelines_page_view.py,sha256=v2pxrIbsHBcYiblfius3JK766NZ7ciD2yPx0t3E5IJo,2656
|
|
290
|
-
workbench-0.8.
|
|
291
|
-
workbench-0.8.
|
|
292
|
-
workbench-0.8.
|
|
293
|
-
workbench-0.8.
|
|
294
|
-
workbench-0.8.
|
|
295
|
-
workbench-0.8.
|
|
290
|
+
workbench-0.8.185.dist-info/licenses/LICENSE,sha256=z4QMMPlLJkZjU8VOKqJkZiQZCEZ--saIU2Z8-p3aVc0,1080
|
|
291
|
+
workbench-0.8.185.dist-info/METADATA,sha256=4S2A5vuZPlJZpRqFGQFe7nL4DzQjTsKIo-V2x0WzDF0,9210
|
|
292
|
+
workbench-0.8.185.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
293
|
+
workbench-0.8.185.dist-info/entry_points.txt,sha256=zPFPruY9uayk8-wsKrhfnIyIB6jvZOW_ibyllEIsLWo,356
|
|
294
|
+
workbench-0.8.185.dist-info/top_level.txt,sha256=Dhy72zTxaA_o_yRkPZx5zw-fwumnjGaeGf0hBN3jc_w,10
|
|
295
|
+
workbench-0.8.185.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|