scitex 2.0.0__py2.py3-none-any.whl → 2.1.0__py2.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.
- scitex/__init__.py +53 -15
- scitex/__main__.py +72 -26
- scitex/__version__.py +1 -1
- scitex/_sh.py +145 -23
- scitex/ai/__init__.py +30 -16
- scitex/ai/_gen_ai/_Anthropic.py +5 -7
- scitex/ai/_gen_ai/_BaseGenAI.py +2 -2
- scitex/ai/_gen_ai/_DeepSeek.py +10 -2
- scitex/ai/_gen_ai/_Google.py +2 -2
- scitex/ai/_gen_ai/_Llama.py +2 -2
- scitex/ai/_gen_ai/_OpenAI.py +2 -2
- scitex/ai/_gen_ai/_PARAMS.py +51 -65
- scitex/ai/_gen_ai/_Perplexity.py +2 -2
- scitex/ai/_gen_ai/__init__.py +25 -14
- scitex/ai/_gen_ai/_format_output_func.py +4 -4
- scitex/ai/classification/{classifier_server.py → Classifier.py} +5 -5
- scitex/ai/classification/CrossValidationExperiment.py +374 -0
- scitex/ai/classification/__init__.py +43 -4
- scitex/ai/classification/reporters/_BaseClassificationReporter.py +281 -0
- scitex/ai/classification/reporters/_ClassificationReporter.py +773 -0
- scitex/ai/classification/reporters/_MultiClassificationReporter.py +406 -0
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +1834 -0
- scitex/ai/classification/reporters/__init__.py +11 -0
- scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1028 -0
- scitex/ai/classification/reporters/reporter_utils/__init__.py +80 -0
- scitex/ai/classification/reporters/reporter_utils/aggregation.py +457 -0
- scitex/ai/classification/reporters/reporter_utils/data_models.py +313 -0
- scitex/ai/classification/reporters/reporter_utils/reporting.py +1056 -0
- scitex/ai/classification/reporters/reporter_utils/storage.py +221 -0
- scitex/ai/classification/reporters/reporter_utils/validation.py +395 -0
- scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +568 -0
- scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +688 -0
- scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +139 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +1716 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +1685 -0
- scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +84 -0
- scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +610 -0
- scitex/ai/classification/timeseries/__init__.py +39 -0
- scitex/ai/classification/timeseries/_normalize_timestamp.py +436 -0
- scitex/ai/clustering/_umap.py +2 -2
- scitex/ai/feature_extraction/vit.py +1 -0
- scitex/ai/feature_selection/__init__.py +30 -0
- scitex/ai/feature_selection/feature_selection.py +364 -0
- scitex/ai/loss/multi_task_loss.py +1 -1
- scitex/ai/metrics/__init__.py +51 -4
- scitex/ai/metrics/_calc_bacc.py +61 -0
- scitex/ai/metrics/_calc_bacc_from_conf_mat.py +38 -0
- scitex/ai/metrics/_calc_clf_report.py +78 -0
- scitex/ai/metrics/_calc_conf_mat.py +93 -0
- scitex/ai/metrics/_calc_feature_importance.py +183 -0
- scitex/ai/metrics/_calc_mcc.py +61 -0
- scitex/ai/metrics/_calc_pre_rec_auc.py +116 -0
- scitex/ai/metrics/_calc_roc_auc.py +110 -0
- scitex/ai/metrics/_calc_seizure_prediction_metrics.py +490 -0
- scitex/ai/metrics/{silhoute_score_block.py → _calc_silhouette_score.py} +15 -8
- scitex/ai/metrics/_normalize_labels.py +83 -0
- scitex/ai/plt/__init__.py +47 -8
- scitex/ai/plt/{_conf_mat.py → _plot_conf_mat.py} +158 -87
- scitex/ai/plt/_plot_feature_importance.py +323 -0
- scitex/ai/plt/_plot_learning_curve.py +345 -0
- scitex/ai/plt/_plot_optuna_study.py +225 -0
- scitex/ai/plt/_plot_pre_rec_curve.py +290 -0
- scitex/ai/plt/_plot_roc_curve.py +255 -0
- scitex/ai/training/{learning_curve_logger.py → _LearningCurveLogger.py} +197 -213
- scitex/ai/training/__init__.py +2 -2
- scitex/ai/utils/grid_search.py +3 -3
- scitex/benchmark/__init__.py +52 -0
- scitex/benchmark/benchmark.py +400 -0
- scitex/benchmark/monitor.py +370 -0
- scitex/benchmark/profiler.py +297 -0
- scitex/browser/__init__.py +48 -0
- scitex/browser/automation/CookieHandler.py +216 -0
- scitex/browser/automation/__init__.py +7 -0
- scitex/browser/collaboration/__init__.py +55 -0
- scitex/browser/collaboration/auth_helpers.py +94 -0
- scitex/browser/collaboration/collaborative_agent.py +136 -0
- scitex/browser/collaboration/credential_manager.py +188 -0
- scitex/browser/collaboration/interactive_panel.py +400 -0
- scitex/browser/collaboration/persistent_browser.py +170 -0
- scitex/browser/collaboration/shared_session.py +383 -0
- scitex/browser/collaboration/standard_interactions.py +246 -0
- scitex/browser/collaboration/visual_feedback.py +181 -0
- scitex/browser/core/BrowserMixin.py +326 -0
- scitex/browser/core/ChromeProfileManager.py +446 -0
- scitex/browser/core/__init__.py +9 -0
- scitex/browser/debugging/__init__.py +18 -0
- scitex/browser/debugging/_browser_logger.py +657 -0
- scitex/browser/debugging/_highlight_element.py +143 -0
- scitex/browser/debugging/_show_grid.py +154 -0
- scitex/browser/interaction/__init__.py +24 -0
- scitex/browser/interaction/click_center.py +149 -0
- scitex/browser/interaction/click_with_fallbacks.py +206 -0
- scitex/browser/interaction/close_popups.py +498 -0
- scitex/browser/interaction/fill_with_fallbacks.py +209 -0
- scitex/browser/pdf/__init__.py +14 -0
- scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +200 -0
- scitex/browser/pdf/detect_chrome_pdf_viewer.py +198 -0
- scitex/browser/remote/CaptchaHandler.py +434 -0
- scitex/browser/remote/ZenRowsAPIClient.py +347 -0
- scitex/browser/remote/ZenRowsBrowserManager.py +570 -0
- scitex/browser/remote/__init__.py +11 -0
- scitex/browser/stealth/HumanBehavior.py +344 -0
- scitex/browser/stealth/StealthManager.py +1008 -0
- scitex/browser/stealth/__init__.py +9 -0
- scitex/browser/template.py +122 -0
- scitex/capture/__init__.py +110 -0
- scitex/capture/__main__.py +25 -0
- scitex/capture/capture.py +848 -0
- scitex/capture/cli.py +233 -0
- scitex/capture/gif.py +344 -0
- scitex/capture/mcp_server.py +961 -0
- scitex/capture/session.py +70 -0
- scitex/capture/utils.py +705 -0
- scitex/cli/__init__.py +17 -0
- scitex/cli/cloud.py +447 -0
- scitex/cli/main.py +42 -0
- scitex/cli/scholar.py +280 -0
- scitex/context/_suppress_output.py +5 -3
- scitex/db/__init__.py +30 -3
- scitex/db/__main__.py +75 -0
- scitex/db/_check_health.py +381 -0
- scitex/db/_delete_duplicates.py +25 -386
- scitex/db/_inspect.py +335 -114
- scitex/db/_inspect_optimized.py +301 -0
- scitex/db/{_PostgreSQL.py → _postgresql/_PostgreSQL.py} +3 -3
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_BackupMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_BatchMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_BlobMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_ConnectionMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_MaintenanceMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_QueryMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_SchemaMixin.py +1 -1
- scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_TransactionMixin.py +1 -1
- scitex/db/_postgresql/__init__.py +6 -0
- scitex/db/_sqlite3/_SQLite3.py +210 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +581 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +517 -0
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_BatchMixin.py +1 -1
- scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +281 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +548 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin_v01-indentation-issues.py +583 -0
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_ConnectionMixin.py +29 -13
- scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +583 -0
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_ImportExportMixin.py +1 -1
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_IndexMixin.py +1 -1
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_MaintenanceMixin.py +2 -1
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_QueryMixin.py +37 -10
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_RowMixin.py +46 -6
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_TableMixin.py +56 -10
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/_TransactionMixin.py +1 -1
- scitex/db/{_SQLite3Mixins → _sqlite3/_SQLite3Mixins}/__init__.py +14 -2
- scitex/db/_sqlite3/__init__.py +7 -0
- scitex/db/_sqlite3/_delete_duplicates.py +274 -0
- scitex/decorators/__init__.py +2 -0
- scitex/decorators/_cache_disk.py +13 -5
- scitex/decorators/_cache_disk_async.py +49 -0
- scitex/decorators/_deprecated.py +175 -10
- scitex/decorators/_timeout.py +1 -1
- scitex/dev/_analyze_code_flow.py +2 -2
- scitex/dict/_DotDict.py +73 -15
- scitex/dict/_DotDict_v01-not-handling-recursive-instantiations.py +442 -0
- scitex/dict/_DotDict_v02-not-serializing-Path-object.py +446 -0
- scitex/dict/__init__.py +2 -0
- scitex/dict/_flatten.py +27 -0
- scitex/dsp/_crop.py +2 -2
- scitex/dsp/_demo_sig.py +2 -2
- scitex/dsp/_detect_ripples.py +2 -2
- scitex/dsp/_hilbert.py +2 -2
- scitex/dsp/_listen.py +6 -6
- scitex/dsp/_modulation_index.py +2 -2
- scitex/dsp/_pac.py +1 -1
- scitex/dsp/_psd.py +2 -2
- scitex/dsp/_resample.py +2 -1
- scitex/dsp/_time.py +3 -2
- scitex/dsp/_wavelet.py +3 -2
- scitex/dsp/add_noise.py +2 -2
- scitex/dsp/example.py +1 -0
- scitex/dsp/filt.py +10 -9
- scitex/dsp/template.py +3 -2
- scitex/dsp/utils/_differential_bandpass_filters.py +1 -1
- scitex/dsp/utils/pac.py +2 -2
- scitex/dt/_normalize_timestamp.py +432 -0
- scitex/errors.py +572 -0
- scitex/gen/_DimHandler.py +2 -2
- scitex/gen/__init__.py +37 -7
- scitex/gen/_deprecated_close.py +80 -0
- scitex/gen/_deprecated_start.py +26 -0
- scitex/gen/_detect_environment.py +152 -0
- scitex/gen/_detect_notebook_path.py +169 -0
- scitex/gen/_embed.py +6 -2
- scitex/gen/_get_notebook_path.py +257 -0
- scitex/gen/_less.py +1 -1
- scitex/gen/_list_packages.py +2 -2
- scitex/gen/_norm.py +44 -9
- scitex/gen/_norm_cache.py +269 -0
- scitex/gen/_src.py +3 -5
- scitex/gen/_title_case.py +3 -3
- scitex/io/__init__.py +28 -6
- scitex/io/_glob.py +13 -7
- scitex/io/_load.py +108 -21
- scitex/io/_load_cache.py +303 -0
- scitex/io/_load_configs.py +40 -15
- scitex/io/{_H5Explorer.py → _load_modules/_H5Explorer.py} +80 -17
- scitex/io/_load_modules/_ZarrExplorer.py +114 -0
- scitex/io/_load_modules/_bibtex.py +207 -0
- scitex/io/_load_modules/_hdf5.py +53 -178
- scitex/io/_load_modules/_json.py +5 -3
- scitex/io/_load_modules/_pdf.py +871 -16
- scitex/io/_load_modules/_sqlite3.py +15 -0
- scitex/io/_load_modules/_txt.py +41 -12
- scitex/io/_load_modules/_yaml.py +4 -3
- scitex/io/_load_modules/_zarr.py +126 -0
- scitex/io/_save.py +429 -171
- scitex/io/_save_modules/__init__.py +6 -0
- scitex/io/_save_modules/_bibtex.py +194 -0
- scitex/io/_save_modules/_csv.py +8 -4
- scitex/io/_save_modules/_excel.py +174 -15
- scitex/io/_save_modules/_hdf5.py +251 -226
- scitex/io/_save_modules/_image.py +1 -3
- scitex/io/_save_modules/_json.py +49 -4
- scitex/io/_save_modules/_listed_dfs_as_csv.py +1 -3
- scitex/io/_save_modules/_listed_scalars_as_csv.py +1 -3
- scitex/io/_save_modules/_tex.py +277 -0
- scitex/io/_save_modules/_yaml.py +42 -3
- scitex/io/_save_modules/_zarr.py +160 -0
- scitex/io/utils/__init__.py +20 -0
- scitex/io/utils/h5_to_zarr.py +616 -0
- scitex/linalg/_geometric_median.py +6 -2
- scitex/{gen/_tee.py → logging/_Tee.py} +43 -84
- scitex/logging/__init__.py +122 -0
- scitex/logging/_config.py +158 -0
- scitex/logging/_context.py +103 -0
- scitex/logging/_formatters.py +128 -0
- scitex/logging/_handlers.py +64 -0
- scitex/logging/_levels.py +35 -0
- scitex/logging/_logger.py +163 -0
- scitex/logging/_print_capture.py +95 -0
- scitex/ml/__init__.py +69 -0
- scitex/{ai/genai/anthropic.py → ml/_gen_ai/_Anthropic.py} +13 -19
- scitex/{ai/genai/base_genai.py → ml/_gen_ai/_BaseGenAI.py} +5 -5
- scitex/{ai/genai/deepseek.py → ml/_gen_ai/_DeepSeek.py} +11 -16
- scitex/{ai/genai/google.py → ml/_gen_ai/_Google.py} +7 -15
- scitex/{ai/genai/groq.py → ml/_gen_ai/_Groq.py} +1 -8
- scitex/{ai/genai/llama.py → ml/_gen_ai/_Llama.py} +3 -16
- scitex/{ai/genai/openai.py → ml/_gen_ai/_OpenAI.py} +3 -3
- scitex/{ai/genai/params.py → ml/_gen_ai/_PARAMS.py} +51 -65
- scitex/{ai/genai/perplexity.py → ml/_gen_ai/_Perplexity.py} +3 -14
- scitex/ml/_gen_ai/__init__.py +43 -0
- scitex/{ai/genai/calc_cost.py → ml/_gen_ai/_calc_cost.py} +1 -1
- scitex/{ai/genai/format_output_func.py → ml/_gen_ai/_format_output_func.py} +4 -4
- scitex/{ai/genai/genai_factory.py → ml/_gen_ai/_genai_factory.py} +8 -8
- scitex/ml/activation/__init__.py +8 -0
- scitex/ml/activation/_define.py +11 -0
- scitex/{ai/classifier_server.py → ml/classification/Classifier.py} +5 -5
- scitex/ml/classification/CrossValidationExperiment.py +374 -0
- scitex/ml/classification/__init__.py +46 -0
- scitex/ml/classification/reporters/_BaseClassificationReporter.py +281 -0
- scitex/ml/classification/reporters/_ClassificationReporter.py +773 -0
- scitex/ml/classification/reporters/_MultiClassificationReporter.py +406 -0
- scitex/ml/classification/reporters/_SingleClassificationReporter.py +1834 -0
- scitex/ml/classification/reporters/__init__.py +11 -0
- scitex/ml/classification/reporters/reporter_utils/_Plotter.py +1028 -0
- scitex/ml/classification/reporters/reporter_utils/__init__.py +80 -0
- scitex/ml/classification/reporters/reporter_utils/aggregation.py +457 -0
- scitex/ml/classification/reporters/reporter_utils/data_models.py +313 -0
- scitex/ml/classification/reporters/reporter_utils/reporting.py +1056 -0
- scitex/ml/classification/reporters/reporter_utils/storage.py +221 -0
- scitex/ml/classification/reporters/reporter_utils/validation.py +395 -0
- scitex/ml/classification/timeseries/_TimeSeriesBlockingSplit.py +568 -0
- scitex/ml/classification/timeseries/_TimeSeriesCalendarSplit.py +688 -0
- scitex/ml/classification/timeseries/_TimeSeriesMetadata.py +139 -0
- scitex/ml/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +1716 -0
- scitex/ml/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +1685 -0
- scitex/ml/classification/timeseries/_TimeSeriesStrategy.py +84 -0
- scitex/ml/classification/timeseries/_TimeSeriesStratifiedSplit.py +610 -0
- scitex/ml/classification/timeseries/__init__.py +39 -0
- scitex/ml/classification/timeseries/_normalize_timestamp.py +436 -0
- scitex/ml/clustering/__init__.py +11 -0
- scitex/ml/clustering/_pca.py +115 -0
- scitex/ml/clustering/_umap.py +376 -0
- scitex/ml/feature_extraction/__init__.py +56 -0
- scitex/ml/feature_extraction/vit.py +149 -0
- scitex/ml/feature_selection/__init__.py +30 -0
- scitex/ml/feature_selection/feature_selection.py +364 -0
- scitex/ml/loss/_L1L2Losses.py +34 -0
- scitex/ml/loss/__init__.py +12 -0
- scitex/ml/loss/multi_task_loss.py +47 -0
- scitex/ml/metrics/__init__.py +56 -0
- scitex/ml/metrics/_calc_bacc.py +61 -0
- scitex/ml/metrics/_calc_bacc_from_conf_mat.py +38 -0
- scitex/ml/metrics/_calc_clf_report.py +78 -0
- scitex/ml/metrics/_calc_conf_mat.py +93 -0
- scitex/ml/metrics/_calc_feature_importance.py +183 -0
- scitex/ml/metrics/_calc_mcc.py +61 -0
- scitex/ml/metrics/_calc_pre_rec_auc.py +116 -0
- scitex/ml/metrics/_calc_roc_auc.py +110 -0
- scitex/ml/metrics/_calc_seizure_prediction_metrics.py +490 -0
- scitex/ml/metrics/_calc_silhouette_score.py +503 -0
- scitex/ml/metrics/_normalize_labels.py +83 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/__init__.py +0 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/ranger/__init__.py +3 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +207 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +238 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +215 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/ranger/rangerqh.py +184 -0
- scitex/ml/optim/Ranger_Deep_Learning_Optimizer/setup.py +24 -0
- scitex/ml/optim/__init__.py +13 -0
- scitex/ml/optim/_get_set.py +31 -0
- scitex/ml/optim/_optimizers.py +71 -0
- scitex/ml/plt/__init__.py +60 -0
- scitex/ml/plt/_plot_conf_mat.py +663 -0
- scitex/ml/plt/_plot_feature_importance.py +323 -0
- scitex/ml/plt/_plot_learning_curve.py +345 -0
- scitex/ml/plt/_plot_optuna_study.py +225 -0
- scitex/ml/plt/_plot_pre_rec_curve.py +290 -0
- scitex/ml/plt/_plot_roc_curve.py +255 -0
- scitex/ml/sk/__init__.py +11 -0
- scitex/ml/sk/_clf.py +58 -0
- scitex/ml/sk/_to_sktime.py +100 -0
- scitex/ml/sklearn/__init__.py +26 -0
- scitex/ml/sklearn/clf.py +58 -0
- scitex/ml/sklearn/to_sktime.py +100 -0
- scitex/{ai/training/early_stopping.py → ml/training/_EarlyStopping.py} +1 -2
- scitex/{ai → ml/training}/_LearningCurveLogger.py +198 -242
- scitex/ml/training/__init__.py +7 -0
- scitex/ml/utils/__init__.py +22 -0
- scitex/ml/utils/_check_params.py +50 -0
- scitex/ml/utils/_default_dataset.py +46 -0
- scitex/ml/utils/_format_samples_for_sktime.py +26 -0
- scitex/ml/utils/_label_encoder.py +134 -0
- scitex/ml/utils/_merge_labels.py +22 -0
- scitex/ml/utils/_sliding_window_data_augmentation.py +11 -0
- scitex/ml/utils/_under_sample.py +51 -0
- scitex/ml/utils/_verify_n_gpus.py +16 -0
- scitex/ml/utils/grid_search.py +148 -0
- scitex/nn/_BNet.py +15 -9
- scitex/nn/_Filters.py +2 -2
- scitex/nn/_ModulationIndex.py +2 -2
- scitex/nn/_PAC.py +1 -1
- scitex/nn/_Spectrogram.py +12 -3
- scitex/nn/__init__.py +9 -10
- scitex/path/__init__.py +18 -0
- scitex/path/_clean.py +4 -0
- scitex/path/_find.py +9 -4
- scitex/path/_symlink.py +348 -0
- scitex/path/_version.py +4 -3
- scitex/pd/__init__.py +2 -0
- scitex/pd/_get_unique.py +99 -0
- scitex/plt/__init__.py +114 -5
- scitex/plt/_subplots/_AxesWrapper.py +1 -3
- scitex/plt/_subplots/_AxisWrapper.py +7 -3
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +47 -13
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +160 -2
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +26 -4
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +322 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +1 -0
- scitex/plt/_subplots/_FigWrapper.py +62 -6
- scitex/plt/_subplots/_export_as_csv.py +43 -27
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +5 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +81 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +20 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +35 -18
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +15 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_conf_mat.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_ecdf.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_fillv.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_heatmap.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_image.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_joyplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_line.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_mean_ci.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_mean_std.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_median_iqr.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_raster.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_rectangle.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +35 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter_hist.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_shaded_line.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_violin.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +6 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +60 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +1 -3
- scitex/plt/_subplots/_export_as_csv_formatters.py +56 -59
- scitex/plt/ax/_style/_hide_spines.py +1 -3
- scitex/plt/ax/_style/_rotate_labels.py +180 -76
- scitex/plt/ax/_style/_rotate_labels_v01.py +248 -0
- scitex/plt/ax/_style/_set_meta.py +11 -4
- scitex/plt/ax/_style/_set_supxyt.py +3 -3
- scitex/plt/ax/_style/_set_xyt.py +3 -3
- scitex/plt/ax/_style/_share_axes.py +2 -2
- scitex/plt/color/__init__.py +4 -4
- scitex/plt/color/{_get_colors_from_cmap.py → _get_colors_from_conf_matap.py} +7 -7
- scitex/plt/utils/_configure_mpl.py +99 -86
- scitex/plt/utils/_histogram_utils.py +1 -3
- scitex/plt/utils/_is_valid_axis.py +1 -3
- scitex/plt/utils/_scitex_config.py +1 -0
- scitex/repro/__init__.py +75 -0
- scitex/{reproduce → repro}/_gen_ID.py +1 -1
- scitex/{reproduce → repro}/_gen_timestamp.py +1 -1
- scitex/repro_rng/_RandomStateManager.py +590 -0
- scitex/repro_rng/_RandomStateManager_v01-no-verbose-options.py +414 -0
- scitex/repro_rng/__init__.py +39 -0
- scitex/reproduce/__init__.py +25 -13
- scitex/reproduce/_hash_array.py +22 -0
- scitex/resource/_get_processor_usages.py +4 -4
- scitex/resource/_get_specs.py +2 -2
- scitex/resource/_log_processor_usages.py +2 -2
- scitex/rng/_RandomStateManager.py +590 -0
- scitex/rng/_RandomStateManager_v01-no-verbose-options.py +414 -0
- scitex/rng/__init__.py +39 -0
- scitex/scholar/__init__.py +309 -19
- scitex/scholar/__main__.py +319 -0
- scitex/scholar/auth/ScholarAuthManager.py +308 -0
- scitex/scholar/auth/__init__.py +12 -0
- scitex/scholar/auth/core/AuthenticationGateway.py +473 -0
- scitex/scholar/auth/core/BrowserAuthenticator.py +386 -0
- scitex/scholar/auth/core/StrategyResolver.py +309 -0
- scitex/scholar/auth/core/__init__.py +16 -0
- scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +120 -0
- scitex/scholar/auth/gateway/_OpenURLResolver.py +209 -0
- scitex/scholar/auth/gateway/__init__.py +38 -0
- scitex/scholar/auth/gateway/_resolve_functions.py +101 -0
- scitex/scholar/auth/providers/BaseAuthenticator.py +166 -0
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +484 -0
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +619 -0
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +686 -0
- scitex/scholar/auth/providers/__init__.py +18 -0
- scitex/scholar/auth/session/AuthCacheManager.py +189 -0
- scitex/scholar/auth/session/SessionManager.py +159 -0
- scitex/scholar/auth/session/__init__.py +11 -0
- scitex/scholar/auth/sso/BaseSSOAutomator.py +373 -0
- scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +378 -0
- scitex/scholar/auth/sso/SSOAutomator.py +180 -0
- scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +380 -0
- scitex/scholar/auth/sso/__init__.py +15 -0
- scitex/scholar/browser/ScholarBrowserManager.py +705 -0
- scitex/scholar/browser/__init__.py +38 -0
- scitex/scholar/browser/utils/__init__.py +13 -0
- scitex/scholar/browser/utils/click_and_wait.py +205 -0
- scitex/scholar/browser/utils/close_unwanted_pages.py +140 -0
- scitex/scholar/browser/utils/wait_redirects.py +732 -0
- scitex/scholar/config/PublisherRules.py +132 -0
- scitex/scholar/config/ScholarConfig.py +126 -0
- scitex/scholar/config/__init__.py +17 -0
- scitex/scholar/core/Paper.py +627 -0
- scitex/scholar/core/Papers.py +722 -0
- scitex/scholar/core/Scholar.py +1975 -0
- scitex/scholar/core/__init__.py +9 -0
- scitex/scholar/impact_factor/ImpactFactorEngine.py +204 -0
- scitex/scholar/impact_factor/__init__.py +20 -0
- scitex/scholar/impact_factor/estimation/ImpactFactorEstimationEngine.py +0 -0
- scitex/scholar/impact_factor/estimation/__init__.py +40 -0
- scitex/scholar/impact_factor/estimation/build_database.py +0 -0
- scitex/scholar/impact_factor/estimation/core/__init__.py +28 -0
- scitex/scholar/impact_factor/estimation/core/cache_manager.py +523 -0
- scitex/scholar/impact_factor/estimation/core/calculator.py +355 -0
- scitex/scholar/impact_factor/estimation/core/journal_matcher.py +428 -0
- scitex/scholar/integration/__init__.py +59 -0
- scitex/scholar/integration/base.py +502 -0
- scitex/scholar/integration/mendeley/__init__.py +22 -0
- scitex/scholar/integration/mendeley/exporter.py +166 -0
- scitex/scholar/integration/mendeley/importer.py +236 -0
- scitex/scholar/integration/mendeley/linker.py +79 -0
- scitex/scholar/integration/mendeley/mapper.py +212 -0
- scitex/scholar/integration/zotero/__init__.py +27 -0
- scitex/scholar/integration/zotero/__main__.py +264 -0
- scitex/scholar/integration/zotero/exporter.py +351 -0
- scitex/scholar/integration/zotero/importer.py +372 -0
- scitex/scholar/integration/zotero/linker.py +415 -0
- scitex/scholar/integration/zotero/mapper.py +286 -0
- scitex/scholar/metadata_engines/ScholarEngine.py +588 -0
- scitex/scholar/metadata_engines/__init__.py +21 -0
- scitex/scholar/metadata_engines/individual/ArXivEngine.py +397 -0
- scitex/scholar/metadata_engines/individual/CrossRefEngine.py +274 -0
- scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +263 -0
- scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +350 -0
- scitex/scholar/metadata_engines/individual/PubMedEngine.py +329 -0
- scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +438 -0
- scitex/scholar/metadata_engines/individual/URLDOIEngine.py +410 -0
- scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +487 -0
- scitex/scholar/metadata_engines/individual/__init__.py +7 -0
- scitex/scholar/metadata_engines/utils/_PubMedConverter.py +469 -0
- scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +283 -0
- scitex/scholar/metadata_engines/utils/__init__.py +30 -0
- scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +103 -0
- scitex/scholar/metadata_engines/utils/_standardize_metadata.py +376 -0
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +579 -0
- scitex/scholar/pdf_download/__init__.py +5 -0
- scitex/scholar/pdf_download/strategies/__init__.py +38 -0
- scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +376 -0
- scitex/scholar/pdf_download/strategies/direct_download.py +131 -0
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +167 -0
- scitex/scholar/pdf_download/strategies/manual_download_utils.py +996 -0
- scitex/scholar/pdf_download/strategies/response_body.py +207 -0
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +364 -0
- scitex/scholar/pipelines/ScholarPipelineParallel.py +478 -0
- scitex/scholar/pipelines/ScholarPipelineSingle.py +767 -0
- scitex/scholar/pipelines/__init__.py +49 -0
- scitex/scholar/storage/BibTeXHandler.py +1018 -0
- scitex/scholar/storage/PaperIO.py +468 -0
- scitex/scholar/storage/ScholarLibrary.py +182 -0
- scitex/scholar/storage/_DeduplicationManager.py +548 -0
- scitex/scholar/storage/_LibraryCacheManager.py +724 -0
- scitex/scholar/storage/_LibraryManager.py +1835 -0
- scitex/scholar/storage/__init__.py +28 -0
- scitex/scholar/url_finder/ScholarURLFinder.py +379 -0
- scitex/scholar/url_finder/__init__.py +7 -0
- scitex/scholar/url_finder/strategies/__init__.py +33 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +261 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_dropdown.py +67 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +204 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +256 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +165 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +163 -0
- scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +70 -0
- scitex/scholar/utils/__init__.py +22 -0
- scitex/scholar/utils/bibtex/__init__.py +9 -0
- scitex/scholar/utils/bibtex/_parse_bibtex.py +71 -0
- scitex/scholar/utils/cleanup/__init__.py +8 -0
- scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +96 -0
- scitex/scholar/utils/cleanup/cleanup_old_extractions.py +117 -0
- scitex/scholar/utils/text/_TextNormalizer.py +407 -0
- scitex/scholar/utils/text/__init__.py +9 -0
- scitex/scholar/zotero/__init__.py +38 -0
- scitex/session/__init__.py +51 -0
- scitex/session/_lifecycle.py +736 -0
- scitex/session/_manager.py +102 -0
- scitex/session/template.py +122 -0
- scitex/stats/__init__.py +30 -26
- scitex/stats/correct/__init__.py +21 -0
- scitex/stats/correct/_correct_bonferroni.py +551 -0
- scitex/stats/correct/_correct_fdr.py +634 -0
- scitex/stats/correct/_correct_holm.py +548 -0
- scitex/stats/correct/_correct_sidak.py +499 -0
- scitex/stats/descriptive/__init__.py +85 -0
- scitex/stats/descriptive/_circular.py +540 -0
- scitex/stats/descriptive/_describe.py +219 -0
- scitex/stats/descriptive/_nan.py +518 -0
- scitex/stats/descriptive/_real.py +189 -0
- scitex/stats/effect_sizes/__init__.py +41 -0
- scitex/stats/effect_sizes/_cliffs_delta.py +325 -0
- scitex/stats/effect_sizes/_cohens_d.py +342 -0
- scitex/stats/effect_sizes/_epsilon_squared.py +315 -0
- scitex/stats/effect_sizes/_eta_squared.py +302 -0
- scitex/stats/effect_sizes/_prob_superiority.py +296 -0
- scitex/stats/posthoc/__init__.py +19 -0
- scitex/stats/posthoc/_dunnett.py +463 -0
- scitex/stats/posthoc/_games_howell.py +383 -0
- scitex/stats/posthoc/_tukey_hsd.py +367 -0
- scitex/stats/power/__init__.py +19 -0
- scitex/stats/power/_power.py +433 -0
- scitex/stats/template.py +119 -0
- scitex/stats/utils/__init__.py +62 -0
- scitex/stats/utils/_effect_size.py +985 -0
- scitex/stats/utils/_formatters.py +270 -0
- scitex/stats/utils/_normalizers.py +927 -0
- scitex/stats/utils/_power.py +433 -0
- scitex/stats_v01/_EffectSizeCalculator.py +488 -0
- scitex/stats_v01/_StatisticalValidator.py +411 -0
- scitex/stats_v01/__init__.py +60 -0
- scitex/stats_v01/_additional_tests.py +415 -0
- scitex/{stats → stats_v01}/_p2stars.py +19 -5
- scitex/stats_v01/_two_sample_tests.py +141 -0
- scitex/stats_v01/desc/__init__.py +83 -0
- scitex/stats_v01/desc/_circular.py +540 -0
- scitex/stats_v01/desc/_describe.py +219 -0
- scitex/stats_v01/desc/_nan.py +518 -0
- scitex/{stats/desc/_nan.py → stats_v01/desc/_nan_v01-20250920_145731.py} +23 -12
- scitex/stats_v01/desc/_real.py +189 -0
- scitex/stats_v01/tests/__corr_test_optimized.py +221 -0
- scitex/stats_v01/tests/_corr_test_optimized.py +179 -0
- scitex/str/__init__.py +1 -3
- scitex/str/_clean_path.py +6 -2
- scitex/str/_latex_fallback.py +267 -160
- scitex/str/_parse.py +44 -36
- scitex/str/_printc.py +1 -3
- scitex/template/__init__.py +87 -0
- scitex/template/_create_project.py +267 -0
- scitex/template/create_pip_project.py +80 -0
- scitex/template/create_research.py +80 -0
- scitex/template/create_singularity.py +80 -0
- scitex/units.py +291 -0
- scitex/utils/_compress_hdf5.py +14 -3
- scitex/utils/_email.py +21 -2
- scitex/utils/_grid.py +6 -4
- scitex/utils/_notify.py +13 -10
- scitex/utils/_verify_scitex_format.py +589 -0
- scitex/utils/_verify_scitex_format_v01.py +370 -0
- scitex/utils/template.py +122 -0
- scitex/web/_search_pubmed.py +62 -16
- scitex-2.1.0.dist-info/LICENSE +21 -0
- scitex-2.1.0.dist-info/METADATA +677 -0
- scitex-2.1.0.dist-info/RECORD +919 -0
- {scitex-2.0.0.dist-info → scitex-2.1.0.dist-info}/WHEEL +1 -1
- scitex-2.1.0.dist-info/entry_points.txt +3 -0
- scitex/ai/__Classifiers.py +0 -101
- scitex/ai/classification/classification_reporter.py +0 -1137
- scitex/ai/classification/classifiers.py +0 -101
- scitex/ai/classification_reporter.py +0 -1161
- scitex/ai/genai/__init__.py +0 -277
- scitex/ai/genai/anthropic_provider.py +0 -320
- scitex/ai/genai/anthropic_refactored.py +0 -109
- scitex/ai/genai/auth_manager.py +0 -200
- scitex/ai/genai/base_provider.py +0 -291
- scitex/ai/genai/chat_history.py +0 -307
- scitex/ai/genai/cost_tracker.py +0 -276
- scitex/ai/genai/deepseek_provider.py +0 -251
- scitex/ai/genai/google_provider.py +0 -228
- scitex/ai/genai/groq_provider.py +0 -248
- scitex/ai/genai/image_processor.py +0 -250
- scitex/ai/genai/llama_provider.py +0 -214
- scitex/ai/genai/mock_provider.py +0 -127
- scitex/ai/genai/model_registry.py +0 -304
- scitex/ai/genai/openai_provider.py +0 -293
- scitex/ai/genai/perplexity_provider.py +0 -205
- scitex/ai/genai/provider_base.py +0 -302
- scitex/ai/genai/provider_factory.py +0 -370
- scitex/ai/genai/response_handler.py +0 -235
- scitex/ai/layer/_Pass.py +0 -21
- scitex/ai/layer/__init__.py +0 -10
- scitex/ai/layer/_switch.py +0 -8
- scitex/ai/metrics/_bACC.py +0 -51
- scitex/ai/plt/_learning_curve.py +0 -194
- scitex/ai/plt/_optuna_study.py +0 -111
- scitex/ai/plt/aucs/__init__.py +0 -2
- scitex/ai/plt/aucs/example.py +0 -60
- scitex/ai/plt/aucs/pre_rec_auc.py +0 -223
- scitex/ai/plt/aucs/roc_auc.py +0 -246
- scitex/ai/sampling/undersample.py +0 -29
- scitex/db/_SQLite3.py +0 -2136
- scitex/db/_SQLite3Mixins/_BlobMixin.py +0 -229
- scitex/gen/_close.py +0 -222
- scitex/gen/_start.py +0 -451
- scitex/general/__init__.py +0 -5
- scitex/io/_load_modules/_db.py +0 -24
- scitex/life/__init__.py +0 -10
- scitex/life/_monitor_rain.py +0 -49
- scitex/reproduce/_fix_seeds.py +0 -45
- scitex/res/__init__.py +0 -5
- scitex/scholar/_local_search.py +0 -454
- scitex/scholar/_paper.py +0 -244
- scitex/scholar/_pdf_downloader.py +0 -325
- scitex/scholar/_search.py +0 -393
- scitex/scholar/_vector_search.py +0 -370
- scitex/scholar/_web_sources.py +0 -457
- scitex/stats/desc/__init__.py +0 -40
- scitex-2.0.0.dist-info/METADATA +0 -307
- scitex-2.0.0.dist-info/RECORD +0 -572
- scitex-2.0.0.dist-info/licenses/LICENSE +0 -7
- /scitex/ai/{act → activation}/__init__.py +0 -0
- /scitex/ai/{act → activation}/_define.py +0 -0
- /scitex/ai/{early_stopping.py → training/_EarlyStopping.py} +0 -0
- /scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_ImportExportMixin.py +0 -0
- /scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_IndexMixin.py +0 -0
- /scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_RowMixin.py +0 -0
- /scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/_TableMixin.py +0 -0
- /scitex/db/{_PostgreSQLMixins → _postgresql/_PostgreSQLMixins}/__init__.py +0 -0
- /scitex/{stats → stats_v01}/_calc_partial_corr.py +0 -0
- /scitex/{stats → stats_v01}/_corr_test_multi.py +0 -0
- /scitex/{stats → stats_v01}/_corr_test_wrapper.py +0 -0
- /scitex/{stats → stats_v01}/_describe_wrapper.py +0 -0
- /scitex/{stats → stats_v01}/_multiple_corrections.py +0 -0
- /scitex/{stats → stats_v01}/_nan_stats.py +0 -0
- /scitex/{stats → stats_v01}/_p2stars_wrapper.py +0 -0
- /scitex/{stats → stats_v01}/_statistical_tests.py +0 -0
- /scitex/{stats/desc/_describe.py → stats_v01/desc/_describe_v01-20250920_145731.py} +0 -0
- /scitex/{stats/desc/_real.py → stats_v01/desc/_real_v01-20250920_145731.py} +0 -0
- /scitex/{stats → stats_v01}/multiple/__init__.py +0 -0
- /scitex/{stats → stats_v01}/multiple/_bonferroni_correction.py +0 -0
- /scitex/{stats → stats_v01}/multiple/_fdr_correction.py +0 -0
- /scitex/{stats → stats_v01}/multiple/_multicompair.py +0 -0
- /scitex/{stats → stats_v01}/tests/__corr_test.py +0 -0
- /scitex/{stats → stats_v01}/tests/__corr_test_multi.py +0 -0
- /scitex/{stats → stats_v01}/tests/__corr_test_single.py +0 -0
- /scitex/{stats → stats_v01}/tests/__init__.py +0 -0
- /scitex/{stats → stats_v01}/tests/_brunner_munzel_test.py +0 -0
- /scitex/{stats → stats_v01}/tests/_nocorrelation_test.py +0 -0
- /scitex/{stats → stats_v01}/tests/_smirnov_grubbs.py +0 -0
- {scitex-2.0.0.dist-info → scitex-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-10-10 07:16:32 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex_repo/src/scitex/scholar/auth/library/_OpenAthensSSOAutomator.py
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import os
|
|
8
|
+
__FILE__ = (
|
|
9
|
+
"./src/scitex/scholar/auth/library/_OpenAthensSSOAutomator.py"
|
|
10
|
+
)
|
|
11
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
12
|
+
# ----------------------------------------
|
|
13
|
+
|
|
14
|
+
__FILE__ = __file__
|
|
15
|
+
|
|
16
|
+
"""OpenAthens page automation for institutional email entry and selection."""
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from playwright.async_api import BrowserContext, Page
|
|
22
|
+
|
|
23
|
+
from scitex.browser.interaction import click_with_fallbacks_async
|
|
24
|
+
from scitex.scholar.config import ScholarConfig
|
|
25
|
+
|
|
26
|
+
from .BaseSSOAutomator import BaseSSOAutomator
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OpenAthensSSOAutomator(BaseSSOAutomator):
|
|
30
|
+
"""Automator for the initial OpenAthens page (my.openathens.net)."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
openathens_email: Optional[str] = None,
|
|
35
|
+
config: Optional[ScholarConfig] = None,
|
|
36
|
+
**kwargs,
|
|
37
|
+
):
|
|
38
|
+
"""Initialize OpenAthens page automator.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
openathens_email: Institutional email for OpenAthens
|
|
42
|
+
config: ScholarConfig instance
|
|
43
|
+
**kwargs: Additional arguments
|
|
44
|
+
"""
|
|
45
|
+
if config is None:
|
|
46
|
+
config = ScholarConfig()
|
|
47
|
+
|
|
48
|
+
# Resolve email from config
|
|
49
|
+
self.openathens_email = config.resolve(
|
|
50
|
+
"openathens_email", openathens_email, default=""
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
super().__init__(**kwargs)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def name(self):
|
|
57
|
+
return self.__class__.__name__
|
|
58
|
+
|
|
59
|
+
def get_institution_name(self) -> str:
|
|
60
|
+
"""Get human-readable name."""
|
|
61
|
+
return "OpenAthens"
|
|
62
|
+
|
|
63
|
+
def get_institution_id(self) -> str:
|
|
64
|
+
"""Get machine-readable ID."""
|
|
65
|
+
return "openathens"
|
|
66
|
+
|
|
67
|
+
def is_sso_page(self, url: str) -> bool:
|
|
68
|
+
"""Check if URL is OpenAthens login page."""
|
|
69
|
+
openathens_patterns = [
|
|
70
|
+
"my.openathens.net",
|
|
71
|
+
"openathens.net/login",
|
|
72
|
+
"openathens.net/?passiveLogin",
|
|
73
|
+
]
|
|
74
|
+
return any(pattern in url.lower() for pattern in openathens_patterns)
|
|
75
|
+
|
|
76
|
+
async def perform_login_async(self, page: Page) -> bool:
|
|
77
|
+
"""Perform OpenAthens email entry and institution selection."""
|
|
78
|
+
try:
|
|
79
|
+
self.logger.info(
|
|
80
|
+
f"{self.name}: Starting OpenAthens page automation"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if not self.openathens_email:
|
|
84
|
+
self.logger.error(
|
|
85
|
+
f"{self.name}: No OpenAthens email configured"
|
|
86
|
+
)
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Step 1: Enter institutional email
|
|
90
|
+
success = await self._enter_institutional_email_async(page)
|
|
91
|
+
if not success:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Step 2: Wait for institution selection to appear and select it
|
|
95
|
+
success = await self._select_institution_async(page)
|
|
96
|
+
if not success:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
# Step 3: Wait for redirect to institution SSO
|
|
100
|
+
success = await self._wait_for_institution_redirect_async(page)
|
|
101
|
+
|
|
102
|
+
return success
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
self.logger.error(
|
|
106
|
+
f"{self.name}: OpenAthens page automation failed: {e}"
|
|
107
|
+
)
|
|
108
|
+
await self._take_debug_screenshot_async(page)
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
async def _enter_institutional_email_async(self, page) -> bool:
|
|
112
|
+
"""Enter institutional email in OpenAthens form."""
|
|
113
|
+
try:
|
|
114
|
+
self.logger.info(
|
|
115
|
+
f"{self.name}: Entering institutional email: {self.openathens_email}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# The correct selector from Puppeteer testing
|
|
119
|
+
email_selector = "#type-ahead"
|
|
120
|
+
|
|
121
|
+
# Wait for and fill the email field
|
|
122
|
+
try:
|
|
123
|
+
await page.wait_for_selector(email_selector, timeout=10000)
|
|
124
|
+
await page.fill(email_selector, self.openathens_email)
|
|
125
|
+
self.logger.info(
|
|
126
|
+
f"{self.name}: Successfully filled OpenAthens email field"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Press Enter to trigger the search/autocomplete
|
|
130
|
+
await page.press(email_selector, "Enter")
|
|
131
|
+
self.logger.info(
|
|
132
|
+
f"{self.name}: Pressed Enter to trigger institution search"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Wait for institution dropdown to appear
|
|
136
|
+
await page.wait_for_timeout(2000)
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.logger.error(
|
|
141
|
+
f"{self.name}: Failed to fill email field: {e}"
|
|
142
|
+
)
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.logger.error(f"{self.name}: Failed to enter email: {e}")
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
async def _submit_email_form_async(self, page: Page) -> bool:
|
|
150
|
+
"""Submit email form or trigger next step."""
|
|
151
|
+
try:
|
|
152
|
+
# Try pressing Enter first
|
|
153
|
+
await page.keyboard.press("Enter")
|
|
154
|
+
await page.wait_for_timeout(1000)
|
|
155
|
+
|
|
156
|
+
# Try clicking submit/continue buttons
|
|
157
|
+
submit_selectors = [
|
|
158
|
+
"button[type='submit']",
|
|
159
|
+
"input[type='submit']",
|
|
160
|
+
"button:has-text('Continue')",
|
|
161
|
+
"button:has-text('Next')",
|
|
162
|
+
"button:has-text('Submit')",
|
|
163
|
+
"button:has-text('Go')",
|
|
164
|
+
".submit-button",
|
|
165
|
+
".continue-button",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
for selector in submit_selectors:
|
|
169
|
+
try:
|
|
170
|
+
element = await page.wait_for_selector(
|
|
171
|
+
selector, timeout=2000
|
|
172
|
+
)
|
|
173
|
+
if element:
|
|
174
|
+
success = await click_with_fallbacks_async(
|
|
175
|
+
page, selector
|
|
176
|
+
)
|
|
177
|
+
if success:
|
|
178
|
+
self.logger.info(
|
|
179
|
+
f"{self.name}: Successfully clicked submit using: {selector}"
|
|
180
|
+
)
|
|
181
|
+
await page.wait_for_timeout(1000)
|
|
182
|
+
return True
|
|
183
|
+
except TimeoutError:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
self.logger.info(
|
|
187
|
+
f"{self.name}: No explicit submit button found, assuming form submitted automatically"
|
|
188
|
+
)
|
|
189
|
+
return True
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
self.logger.error(f"{self.name}: Failed to submit email form: {e}")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
async def _select_institution_async(self, page) -> bool:
|
|
196
|
+
"""Select institution from the list that appears (generic for any institution)."""
|
|
197
|
+
try:
|
|
198
|
+
self.logger.info(
|
|
199
|
+
f"{self.name}: Waiting for institution selection to appear..."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
institution_selector = ".wayfinder-item"
|
|
203
|
+
await page.wait_for_selector(institution_selector, timeout=10000)
|
|
204
|
+
self.logger.info("Found institution dropdown options")
|
|
205
|
+
|
|
206
|
+
institution_elements = await page.query_selector_all(
|
|
207
|
+
institution_selector
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if len(institution_elements) == 1:
|
|
211
|
+
# Get text BEFORE clicking
|
|
212
|
+
institution_text = await institution_elements[0].text_content()
|
|
213
|
+
await institution_elements[0].click()
|
|
214
|
+
self.logger.info(
|
|
215
|
+
f"{self.name}: Selected institution: {institution_text.strip()}"
|
|
216
|
+
)
|
|
217
|
+
elif len(institution_elements) > 1:
|
|
218
|
+
selected = await self._select_best_institution_match(
|
|
219
|
+
page, institution_elements
|
|
220
|
+
)
|
|
221
|
+
if not selected:
|
|
222
|
+
# Get text BEFORE clicking
|
|
223
|
+
institution_text = await institution_elements[
|
|
224
|
+
0
|
|
225
|
+
].text_content()
|
|
226
|
+
await institution_elements[0].click()
|
|
227
|
+
self.logger.info(
|
|
228
|
+
f"{self.name}: Selected first available institution: {institution_text.strip()}"
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
self.logger.error(f"{self.name}: No institution options found")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
await page.wait_for_timeout(2000)
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.logger.error(
|
|
239
|
+
f"{self.name}: Failed to select institution: {e}"
|
|
240
|
+
)
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
async def _select_best_institution_match(
|
|
244
|
+
self, page, institution_elements
|
|
245
|
+
) -> bool:
|
|
246
|
+
"""Try to select the best matching institution based on email domain."""
|
|
247
|
+
try:
|
|
248
|
+
email_domain = (
|
|
249
|
+
self.openathens_email.split("@")[1].lower()
|
|
250
|
+
if "@" in self.openathens_email
|
|
251
|
+
else ""
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Domain-specific matching logic
|
|
255
|
+
domain_keywords = {
|
|
256
|
+
"unimelb.edu.au": ["melbourne", "unimelb"],
|
|
257
|
+
"stanford.edu": ["stanford"],
|
|
258
|
+
"mit.edu": ["mit", "massachusetts"],
|
|
259
|
+
"harvard.edu": ["harvard"],
|
|
260
|
+
"oxford.ac.uk": ["oxford"],
|
|
261
|
+
"cambridge.ac.uk": ["cambridge"],
|
|
262
|
+
# Add more mappings as needed
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if email_domain in domain_keywords:
|
|
266
|
+
keywords = domain_keywords[email_domain]
|
|
267
|
+
|
|
268
|
+
for element in institution_elements:
|
|
269
|
+
text = await element.text_content()
|
|
270
|
+
text_lower = text.lower()
|
|
271
|
+
|
|
272
|
+
if any(keyword in text_lower for keyword in keywords):
|
|
273
|
+
await element.click()
|
|
274
|
+
self.logger.info(
|
|
275
|
+
f"{self.name}: Selected matched institution: {text.strip()}"
|
|
276
|
+
)
|
|
277
|
+
return True
|
|
278
|
+
|
|
279
|
+
# Generic fallback - look for university in the name
|
|
280
|
+
if "university" in email_domain or "edu" in email_domain:
|
|
281
|
+
for element in institution_elements:
|
|
282
|
+
text = await element.text_content()
|
|
283
|
+
if "university" in text.lower():
|
|
284
|
+
await element.click()
|
|
285
|
+
self.logger.info(
|
|
286
|
+
f"{self.name}: Selected university institution: {text.strip()}"
|
|
287
|
+
)
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
return False
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
self.logger.error(f"{self.name}: Failed to match institution: {e}")
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
async def _wait_for_institution_redirect_async(self, page) -> bool:
|
|
297
|
+
"""Wait for redirect to institution SSO page."""
|
|
298
|
+
try:
|
|
299
|
+
self.logger.info(
|
|
300
|
+
f"{self.name}: Waiting for redirect to institution SSO..."
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Wait for URL to change away from OpenAthens
|
|
304
|
+
for i in range(30): # Wait up to 30 seconds
|
|
305
|
+
current_url = page.url
|
|
306
|
+
|
|
307
|
+
if not self.is_sso_page(current_url):
|
|
308
|
+
self.logger.info(
|
|
309
|
+
f"{self.name}: Redirected to institution SSO: {current_url[:50]}..."
|
|
310
|
+
)
|
|
311
|
+
return True
|
|
312
|
+
|
|
313
|
+
await page.wait_for_timeout(1000)
|
|
314
|
+
|
|
315
|
+
self.logger.error(
|
|
316
|
+
f"{self.name}: Timeout waiting for institution redirect"
|
|
317
|
+
)
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
self.logger.error(f"{self.name}: Failed waiting for redirect: {e}")
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
async def _take_debug_screenshot_async(self, page: Page):
|
|
325
|
+
"""Take debug screenshot."""
|
|
326
|
+
try:
|
|
327
|
+
import time
|
|
328
|
+
from pathlib import Path
|
|
329
|
+
|
|
330
|
+
screenshot_path = (
|
|
331
|
+
Path.home()
|
|
332
|
+
/ ".scitex"
|
|
333
|
+
/ "scholar"
|
|
334
|
+
/ f"openathens_debug_{int(time.time())}.png"
|
|
335
|
+
)
|
|
336
|
+
screenshot_path.parent.mkdir(parents=True, exist_ok=True)
|
|
337
|
+
await page.screenshot(path=str(screenshot_path))
|
|
338
|
+
self.logger.debug(
|
|
339
|
+
f"{self.name}: Debug screenshot: {screenshot_path}"
|
|
340
|
+
)
|
|
341
|
+
except Exception as e:
|
|
342
|
+
self.logger.debug(f"{self.name}: Screenshot failed: {e}")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
import asyncio
|
|
347
|
+
|
|
348
|
+
def main():
|
|
349
|
+
"""Test OpenAthens page automator."""
|
|
350
|
+
from playwright.async_api import async_playwright
|
|
351
|
+
|
|
352
|
+
async def test_automator():
|
|
353
|
+
automator = OpenAthensSSOAutomator()
|
|
354
|
+
|
|
355
|
+
async with async_playwright() as p:
|
|
356
|
+
browser = await p.chromium.launch(headless=False)
|
|
357
|
+
page = await browser.new_page()
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
await page.goto(
|
|
361
|
+
"https://my.openathens.net/?passiveLogin=false"
|
|
362
|
+
)
|
|
363
|
+
success = await automator.perform_login_async(page)
|
|
364
|
+
print(f"OpenAthens automation success: {success}")
|
|
365
|
+
|
|
366
|
+
await page.wait_for_timeout(10000)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
print(f"Error: {e}")
|
|
369
|
+
finally:
|
|
370
|
+
await browser.close()
|
|
371
|
+
|
|
372
|
+
asyncio.run(test_automator())
|
|
373
|
+
|
|
374
|
+
main()
|
|
375
|
+
|
|
376
|
+
# python -m scitex.scholar.auth.sso_automation._OpenAthensSSOAutomator
|
|
377
|
+
|
|
378
|
+
# EOF
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-08-23 11:25:43 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/SciTeX-Code/src/scitex/scholar/auth/sso_automation/_SSOAutomator.py
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import os
|
|
8
|
+
__FILE__ = __file__
|
|
9
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
10
|
+
# ----------------------------------------
|
|
11
|
+
|
|
12
|
+
"""Factory for creating SSO automators."""
|
|
13
|
+
|
|
14
|
+
from typing import Dict, Optional
|
|
15
|
+
|
|
16
|
+
from scitex import logging
|
|
17
|
+
from scitex.scholar.config import ScholarConfig
|
|
18
|
+
|
|
19
|
+
from .BaseSSOAutomator import BaseSSOAutomator
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SSOAutomator:
|
|
25
|
+
"""Factory for creating institution-specific SSO automators."""
|
|
26
|
+
|
|
27
|
+
# Email domain mappings
|
|
28
|
+
EMAIL_DOMAIN_MAP = {
|
|
29
|
+
"@unimelb.edu.au": "UniversityOfMelbourne",
|
|
30
|
+
"@student.unimelb.edu.au": "UniversityOfMelbourne",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# URL pattern mappings
|
|
34
|
+
URL_PATTERN_MAP = {
|
|
35
|
+
"unimelb": "UniversityOfMelbourne",
|
|
36
|
+
"melbourne.edu.au": "UniversityOfMelbourne",
|
|
37
|
+
"exlibrisgroup.com/sfxlcl41": "UniversityOfMelbourne",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Institution name mappings
|
|
41
|
+
INSTITUTION_NAME_MAP = {
|
|
42
|
+
"unimelb": "UniversityOfMelbourne",
|
|
43
|
+
"university of melbourne": "UniversityOfMelbourne",
|
|
44
|
+
"melbourne": "UniversityOfMelbourne",
|
|
45
|
+
"melbourne university": "UniversityOfMelbourne",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Automator class mappings
|
|
49
|
+
AUTOMATOR_CLASS_MAP = {
|
|
50
|
+
"UniversityOfMelbourne": "UniversityOfMelbourneSSOAutomator.UniversityOfMelbourneSSOAutomator",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def __new__(cls, institution=None, email=None, url=None, **kwargs):
|
|
54
|
+
if institution:
|
|
55
|
+
return cls.create_by_name(institution, **kwargs)
|
|
56
|
+
elif email:
|
|
57
|
+
return cls.create_from_email(email, **kwargs)
|
|
58
|
+
elif url:
|
|
59
|
+
return cls.create_from_url(url, **kwargs)
|
|
60
|
+
else:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def create_from_email(
|
|
65
|
+
cls, email: str, **kwargs
|
|
66
|
+
) -> Optional[BaseSSOAutomator]:
|
|
67
|
+
"""Create SSO automator based on email domain."""
|
|
68
|
+
if not email:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
email_lower = email.lower()
|
|
72
|
+
|
|
73
|
+
for domain, automator_name in cls.EMAIL_DOMAIN_MAP.items():
|
|
74
|
+
if email_lower.endswith(domain):
|
|
75
|
+
return cls._create_automator(automator_name, **kwargs)
|
|
76
|
+
|
|
77
|
+
logger.debug(f"No SSO automator found for email: {email}")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def create_from_url(cls, url: str, **kwargs) -> Optional[BaseSSOAutomator]:
|
|
82
|
+
"""Auto-detect institution from URL and create appropriate automator."""
|
|
83
|
+
url_lower = url.lower()
|
|
84
|
+
|
|
85
|
+
for pattern, automator_name in cls.URL_PATTERN_MAP.items():
|
|
86
|
+
if pattern in url_lower:
|
|
87
|
+
logger.info(
|
|
88
|
+
f"Detected {automator_name.replace('Of', ' of ')} from URL"
|
|
89
|
+
)
|
|
90
|
+
return cls._create_automator(automator_name, **kwargs)
|
|
91
|
+
|
|
92
|
+
logger.debug(f"No SSO automator found for URL: {url}")
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def create_by_name(
|
|
97
|
+
cls, institution_name: str, **kwargs
|
|
98
|
+
) -> Optional[BaseSSOAutomator]:
|
|
99
|
+
"""Create SSO automator by institution name."""
|
|
100
|
+
name_lower = institution_name.lower()
|
|
101
|
+
|
|
102
|
+
for key, automator_name in cls.INSTITUTION_NAME_MAP.items():
|
|
103
|
+
if key in name_lower:
|
|
104
|
+
return cls._create_automator(automator_name, **kwargs)
|
|
105
|
+
|
|
106
|
+
logger.warning(
|
|
107
|
+
f"No SSO automator found for institution: {institution_name}"
|
|
108
|
+
)
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def _create_automator(
|
|
113
|
+
cls, automator_name: str, **kwargs
|
|
114
|
+
) -> Optional[BaseSSOAutomator]:
|
|
115
|
+
"""Create automator instance by name."""
|
|
116
|
+
if automator_name not in cls.AUTOMATOR_CLASS_MAP:
|
|
117
|
+
logger.error(f"Unknown automator: {automator_name}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
module_path, class_name = cls.AUTOMATOR_CLASS_MAP[
|
|
122
|
+
automator_name
|
|
123
|
+
].rsplit(".", 1)
|
|
124
|
+
from importlib import import_module
|
|
125
|
+
|
|
126
|
+
module = import_module(f".{module_path}", package=__package__)
|
|
127
|
+
automator_class = getattr(module, class_name)
|
|
128
|
+
return automator_class(**kwargs)
|
|
129
|
+
except ImportError as e:
|
|
130
|
+
logger.error(f"Failed to import automator {automator_name}: {e}")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def list_supported_institutions(cls) -> Dict[str, str]:
|
|
135
|
+
"""Get list of supported institutions."""
|
|
136
|
+
return {
|
|
137
|
+
"unimelb": "University of Melbourne",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
import asyncio
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
"""Test UniMelb SSO automator."""
|
|
146
|
+
from playwright.async_api import async_playwright
|
|
147
|
+
|
|
148
|
+
from scitex.scholar.auth.sso.SSOAutomator import SSOAutomator
|
|
149
|
+
|
|
150
|
+
automator = SSOAutomator(email=os.getenv("SCITEX_SCHOLAR_SSO_EMAIL"))
|
|
151
|
+
|
|
152
|
+
async def test_automator():
|
|
153
|
+
# automator = UniversityOfMelbourneSSOAutomator()
|
|
154
|
+
automator = SSOAutomator(
|
|
155
|
+
email=os.getenv("SCITEX_SCHOLAR_SSO_EMAIL")
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
async with async_playwright() as p:
|
|
159
|
+
browser = await p.chromium.launch(headless=False)
|
|
160
|
+
page = await browser.new_page()
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
await page.goto("https://sso.unimelb.edu.au/")
|
|
164
|
+
success = await automator.perform_login_async(page)
|
|
165
|
+
print(f"Login success: {success}")
|
|
166
|
+
|
|
167
|
+
await page.wait_for_timeout(5000)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f"Error: {e}")
|
|
170
|
+
finally:
|
|
171
|
+
await browser.close()
|
|
172
|
+
|
|
173
|
+
asyncio.run(test_automator())
|
|
174
|
+
|
|
175
|
+
main()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# python -m scitex.scholar.auth.sso_automation._SSOAutomator
|
|
179
|
+
|
|
180
|
+
# EOF
|