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,619 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-10-11 23:50:33 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex_repo/src/scitex/scholar/auth/providers/OpenAthensAuthenticator.py
|
|
5
|
+
# ----------------------------------------
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import os
|
|
8
|
+
__FILE__ = (
|
|
9
|
+
"./src/scitex/scholar/auth/providers/OpenAthensAuthenticator.py"
|
|
10
|
+
)
|
|
11
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
12
|
+
# ----------------------------------------
|
|
13
|
+
|
|
14
|
+
"""OpenAthens authentication for institutional access to academic papers.
|
|
15
|
+
|
|
16
|
+
This module provides authentication through OpenAthens single sign-on
|
|
17
|
+
to enable legal PDF downloads via institutional subscriptions.
|
|
18
|
+
|
|
19
|
+
This refactored version uses smaller, focused helper classes:
|
|
20
|
+
- SessionManager: Handles session state and validation
|
|
21
|
+
- AuthCacheManager: Handles session caching operations
|
|
22
|
+
- AuthLockManager: Handles concurrent authentication prevention
|
|
23
|
+
- BrowserAuthenticator: Handles browser-based authentication
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import asyncio
|
|
27
|
+
from datetime import datetime, timedelta
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
from playwright.async_api import async_playwright
|
|
31
|
+
|
|
32
|
+
from scitex import logging
|
|
33
|
+
from scitex.errors import ScholarError
|
|
34
|
+
from scitex.scholar.config import ScholarConfig
|
|
35
|
+
|
|
36
|
+
from ..core.BrowserAuthenticator import BrowserAuthenticator
|
|
37
|
+
from ..session import AuthCacheManager, SessionManager
|
|
38
|
+
from .BaseAuthenticator import BaseAuthenticator
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OpenAthensError(ScholarError):
|
|
44
|
+
"""Raised when OpenAthens authentication fails."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OpenAthensAuthenticator(BaseAuthenticator):
|
|
50
|
+
"""Handles OpenAthens authentication for institutional access.
|
|
51
|
+
|
|
52
|
+
OpenAthens is a single sign-on system used by many universities
|
|
53
|
+
and institutions to provide seamless access to academic resources.
|
|
54
|
+
|
|
55
|
+
This refactored authenticator:
|
|
56
|
+
1. Uses SessionManager for session state management
|
|
57
|
+
2. Uses AuthCacheManager for session persistence
|
|
58
|
+
3. Uses AuthLockManager for concurrent authentication prevention
|
|
59
|
+
4. Uses BrowserAuthenticator for browser-based operations
|
|
60
|
+
5. Maintains backward compatibility with original interface
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
MYATHENS_URL = "https://my.openathens.net/?passiveLogin=false"
|
|
64
|
+
VERIFICATION_URL = "https://my.openathens.net/account"
|
|
65
|
+
SUCCESS_INDICATORS = [
|
|
66
|
+
"my.openathens.net/account",
|
|
67
|
+
"my.openathens.net/app",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
email: Optional[str] = None,
|
|
73
|
+
timeout: int = 300,
|
|
74
|
+
debug_mode: Optional[bool] = None,
|
|
75
|
+
config: Optional[ScholarConfig] = None,
|
|
76
|
+
):
|
|
77
|
+
"""Initialize OpenAthens authenticator.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
email: Institutional email for identification (uses config if None)
|
|
81
|
+
timeout: Authentication timeout in seconds
|
|
82
|
+
debug_mode: Enable debug logging (uses config if None)
|
|
83
|
+
config: ScholarConfig instance (creates new if None)
|
|
84
|
+
"""
|
|
85
|
+
# Initialize config first
|
|
86
|
+
if config is None:
|
|
87
|
+
config = ScholarConfig()
|
|
88
|
+
self.scholar_config = config # Store ScholarConfig separately from BaseAuthenticator's config
|
|
89
|
+
|
|
90
|
+
# Use config resolution for email and debug_mode
|
|
91
|
+
self.email = self.scholar_config.resolve(
|
|
92
|
+
"openathens_email", email, None, str
|
|
93
|
+
)
|
|
94
|
+
self.debug_mode = self.scholar_config.resolve(
|
|
95
|
+
"debug_mode", debug_mode, False, bool
|
|
96
|
+
)
|
|
97
|
+
self.timeout = timeout # Keep timeout as passed parameter
|
|
98
|
+
|
|
99
|
+
BaseAuthenticator.__init__(
|
|
100
|
+
self, config={"email": self.email, "debug_mode": self.debug_mode}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Initialize helper components
|
|
104
|
+
self.session_manager = SessionManager(default_expiry_hours=8)
|
|
105
|
+
self.cache_manager = AuthCacheManager(
|
|
106
|
+
"openathens", self.scholar_config, self.email
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Create SSO automator for institution-specific automation
|
|
110
|
+
sso_automator = self._create_sso_automator(
|
|
111
|
+
openathens_email=self.email, config=self.scholar_config
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.browser_authenticator = BrowserAuthenticator(
|
|
115
|
+
mode="interactive", timeout=timeout, sso_automator=sso_automator
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def _create_sso_automator(self, openathens_email=None, config=None):
|
|
119
|
+
"""Create appropriate SSO automator based on email domain."""
|
|
120
|
+
try:
|
|
121
|
+
from ..sso import SSOAutomator
|
|
122
|
+
|
|
123
|
+
openathens_email = config.resolve(
|
|
124
|
+
"openathens_email", openathens_email, default=None
|
|
125
|
+
)
|
|
126
|
+
return SSOAutomator(email=openathens_email, config=config)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning(f"SSO automators not available\n{str(e)}")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
async def _ensure_session_loaded_async(self) -> None:
|
|
132
|
+
"""Ensure session data is loaded from cache if available."""
|
|
133
|
+
if not self.session_manager.has_valid_session_data():
|
|
134
|
+
await self.cache_manager.load_session_async(self.session_manager)
|
|
135
|
+
|
|
136
|
+
async def authenticate_async(self, force: bool = False, **kwargs) -> dict:
|
|
137
|
+
"""Authenticate with OpenAthens and return session data.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
force: Force re-authentication even if session exists
|
|
141
|
+
**kwargs: Additional parameters
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dictionary containing session cookies
|
|
145
|
+
"""
|
|
146
|
+
# Check if we have a valid session
|
|
147
|
+
if not force and await self.is_authenticate_async():
|
|
148
|
+
logger.info(
|
|
149
|
+
f"Using existing OpenAthens session{self.session_manager.format_expiry_info()}"
|
|
150
|
+
)
|
|
151
|
+
return self.session_manager.create_auth_response()
|
|
152
|
+
|
|
153
|
+
# No lock needed - workers read cached auth concurrently
|
|
154
|
+
# Only one worker should authenticate at a time, but if cache is valid,
|
|
155
|
+
# all workers skip this section
|
|
156
|
+
try:
|
|
157
|
+
# Double-check session after acquiring lock
|
|
158
|
+
await self._ensure_session_loaded_async()
|
|
159
|
+
if not force and await self.is_authenticate_async():
|
|
160
|
+
logger.info(
|
|
161
|
+
f"Using session authenticate_async by another process{self.session_manager.format_expiry_info()}"
|
|
162
|
+
)
|
|
163
|
+
return self.session_manager.create_auth_response()
|
|
164
|
+
|
|
165
|
+
# Perform browser-based authentication
|
|
166
|
+
return await self._perform_browser_authentication_async()
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
# Check if another process authenticated while we were waiting
|
|
170
|
+
await self._ensure_session_loaded_async()
|
|
171
|
+
if await self.is_authenticated_async():
|
|
172
|
+
logger.info(
|
|
173
|
+
f"{self.name}: Another process authenticated successfully"
|
|
174
|
+
)
|
|
175
|
+
return self.session_manager.create_auth_response()
|
|
176
|
+
raise
|
|
177
|
+
|
|
178
|
+
async def _perform_browser_authentication_async(self) -> dict:
|
|
179
|
+
"""Perform OpenAthens authentication with automatic SSO automation."""
|
|
180
|
+
logger.info(f"{self.name}: Starting OpenAthens authentication")
|
|
181
|
+
if self.email:
|
|
182
|
+
logger.info(f"Account: {self.email}")
|
|
183
|
+
|
|
184
|
+
# Note: Email notification will be sent later when 2FA is required
|
|
185
|
+
# await self._notify_user_intervention_needed_async()
|
|
186
|
+
|
|
187
|
+
async with async_playwright() as p:
|
|
188
|
+
# Always start with OpenAthens page
|
|
189
|
+
page = await self.browser_authenticator.navigate_to_login_async(
|
|
190
|
+
self.MYATHENS_URL
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Display simple instructions
|
|
194
|
+
self._display_login_instructions()
|
|
195
|
+
|
|
196
|
+
# Wait for login completion (includes all automation)
|
|
197
|
+
success = await self.browser_authenticator.wait_for_login_completion_async(
|
|
198
|
+
page, self.SUCCESS_INDICATORS
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if success:
|
|
202
|
+
# Send success notification
|
|
203
|
+
await self._notify_authentication_success_async()
|
|
204
|
+
return await self._handle_successful_authentication_async(page)
|
|
205
|
+
else:
|
|
206
|
+
# Authentication failed
|
|
207
|
+
await self._notify_authentication_failed_async(
|
|
208
|
+
"Authentication timed out"
|
|
209
|
+
)
|
|
210
|
+
await page.context.browser.close()
|
|
211
|
+
raise OpenAthensError("Authentication failed or timed out")
|
|
212
|
+
|
|
213
|
+
async def _handle_successful_authentication_async(self, page) -> dict:
|
|
214
|
+
"""Handle successful authentication by extracting and saving session."""
|
|
215
|
+
# Extract session cookies
|
|
216
|
+
simple_cookies, full_cookies = (
|
|
217
|
+
await self.browser_authenticator.extract_session_cookies_async(
|
|
218
|
+
page
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Update session manager
|
|
223
|
+
expiry = datetime.now() + timedelta(hours=8)
|
|
224
|
+
self.session_manager.set_session_data(
|
|
225
|
+
simple_cookies, full_cookies, expiry
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Save to cache
|
|
229
|
+
await self.cache_manager.save_session_async(self.session_manager)
|
|
230
|
+
|
|
231
|
+
logger.info(
|
|
232
|
+
f"OpenAthens authentication successful{self.session_manager.format_expiry_info()}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
await page.context.browser.close()
|
|
236
|
+
return self.session_manager.create_auth_response()
|
|
237
|
+
|
|
238
|
+
async def is_authenticate_async(self, verify_live: bool = True) -> bool:
|
|
239
|
+
"""Check if we have a valid authenticate_async session.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
verify_live: If True, performs a live check against OpenAthens
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if authenticate_async, False otherwise
|
|
246
|
+
"""
|
|
247
|
+
# Ensure session data is loaded
|
|
248
|
+
await self._ensure_session_loaded_async()
|
|
249
|
+
|
|
250
|
+
# Quick local checks
|
|
251
|
+
if not self.session_manager.has_valid_session_data():
|
|
252
|
+
logger.debug(f"{self.name}: No cookies or session expiry found")
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
if self.session_manager.is_session_expired():
|
|
256
|
+
logger.warning("OpenAthens session expired")
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
expiry = self.session_manager.get_session_async_expiry()
|
|
260
|
+
logger.debug(f"Session valid until {expiry}")
|
|
261
|
+
|
|
262
|
+
# Perform live verification if requested
|
|
263
|
+
if verify_live:
|
|
264
|
+
return await self._verify_live_authentication_async()
|
|
265
|
+
|
|
266
|
+
return True
|
|
267
|
+
|
|
268
|
+
async def _verify_live_authentication_async(self) -> bool:
|
|
269
|
+
"""Verify authentication with live check if needed."""
|
|
270
|
+
if not self.session_manager.needs_live_verification():
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
cookies = self.session_manager.get_full_cookies()
|
|
274
|
+
is_authenticate_async = (
|
|
275
|
+
await self.browser_authenticator.verify_authentication_async(
|
|
276
|
+
self.VERIFICATION_URL, cookies
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if is_authenticate_async:
|
|
281
|
+
self.session_manager.mark_live_verification()
|
|
282
|
+
|
|
283
|
+
return is_authenticate_async
|
|
284
|
+
|
|
285
|
+
async def get_auth_headers_async(self) -> Dict[str, str]:
|
|
286
|
+
"""Get authentication headers (OpenAthens uses cookies, not headers)."""
|
|
287
|
+
return {}
|
|
288
|
+
|
|
289
|
+
async def get_auth_cookies_async(self) -> List[Dict[str, Any]]:
|
|
290
|
+
"""Get authentication cookies."""
|
|
291
|
+
if not await self.is_authenticate_async():
|
|
292
|
+
raise OpenAthensError("Not authenticate_async")
|
|
293
|
+
return self.session_manager.get_full_cookies()
|
|
294
|
+
|
|
295
|
+
async def logout_async(self, clear_cache=False) -> None:
|
|
296
|
+
"""Log out and clear authentication state."""
|
|
297
|
+
self.session_manager.reset_session()
|
|
298
|
+
|
|
299
|
+
# Clear cache if requested
|
|
300
|
+
if clear_cache:
|
|
301
|
+
self.cache_manager.clear_cache()
|
|
302
|
+
|
|
303
|
+
logger.info(f"{self.name}: Logged out from OpenAthens")
|
|
304
|
+
|
|
305
|
+
def _display_login_instructions(self) -> None:
|
|
306
|
+
"""Display simple login instructions to user."""
|
|
307
|
+
logger.info(f"{self.name}: OpenAthens Authentication")
|
|
308
|
+
logger.info(f"{self.name}: This will automatically:")
|
|
309
|
+
logger.info(f"{self.name}: 1. Fill in your institutional email")
|
|
310
|
+
logger.info(f"{self.name}: 2. Select your institution")
|
|
311
|
+
logger.info(f"{self.name}: 3. Handle institution SSO if needed")
|
|
312
|
+
logger.info(f"{self.name}: 4. Manual completion if automation fails")
|
|
313
|
+
|
|
314
|
+
if self.email:
|
|
315
|
+
logger.info(f"Account: {self.email}")
|
|
316
|
+
logger.info(f"Timeout: {self.timeout} seconds")
|
|
317
|
+
|
|
318
|
+
async def get_session_info_async(self) -> Dict[str, Any]:
|
|
319
|
+
"""Get information about current session."""
|
|
320
|
+
if not await self.is_authenticate_async():
|
|
321
|
+
return {"authenticate_async": False}
|
|
322
|
+
|
|
323
|
+
session_info = self.session_manager.get_session_info_async()
|
|
324
|
+
session_info.update(
|
|
325
|
+
{
|
|
326
|
+
"authenticate_async": True,
|
|
327
|
+
"email": self.email,
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
return session_info
|
|
331
|
+
|
|
332
|
+
async def _notify_user_intervention_needed_async(self) -> None:
|
|
333
|
+
"""Send email notification that user intervention is needed for OpenAthens authentication."""
|
|
334
|
+
try:
|
|
335
|
+
from ..utils._email import send_email_async
|
|
336
|
+
|
|
337
|
+
# Get notification email addresses from config
|
|
338
|
+
to_email = self.scholar_config.resolve(
|
|
339
|
+
"notification_email", None, None, str
|
|
340
|
+
)
|
|
341
|
+
from_email = self.scholar_config.resolve(
|
|
342
|
+
"notification_from_email", None, "agent@scitex.ai", str
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
if not to_email:
|
|
346
|
+
logger.debug(
|
|
347
|
+
f"{self.name}: No email address configured for notifications"
|
|
348
|
+
)
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
subject = "SciTeX Scholar: OpenAthens Authentication Required"
|
|
352
|
+
message = f"""
|
|
353
|
+
OpenAthens Authentication Required
|
|
354
|
+
|
|
355
|
+
The SciTeX Scholar system requires your intervention to complete OpenAthens authentication.
|
|
356
|
+
|
|
357
|
+
Details:
|
|
358
|
+
- System: SciTeX Scholar Module
|
|
359
|
+
- Service: OpenAthens Single Sign-On
|
|
360
|
+
- Account: {self.email or 'Not specified'}
|
|
361
|
+
- Timeout: {self.timeout} seconds
|
|
362
|
+
- Login URL: {self.MYATHENS_URL}
|
|
363
|
+
|
|
364
|
+
Action Required:
|
|
365
|
+
1. A browser window should have opened automatically
|
|
366
|
+
2. Complete the OpenAthens login process:
|
|
367
|
+
• Enter your institutional email
|
|
368
|
+
• Select your institution when it appears
|
|
369
|
+
• Complete login on your institution's page
|
|
370
|
+
• You'll be redirected back to OpenAthens when done
|
|
371
|
+
3. The system will continue automatically once authenticate_async
|
|
372
|
+
|
|
373
|
+
If the browser didn't open or you missed it, you can manually navigate to:
|
|
374
|
+
{self.MYATHENS_URL}
|
|
375
|
+
|
|
376
|
+
This is an automated notification from the SciTeX Scholar authentication system.
|
|
377
|
+
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
378
|
+
""".strip()
|
|
379
|
+
|
|
380
|
+
success = await send_email_async(
|
|
381
|
+
from_email=from_email,
|
|
382
|
+
to_email=to_email,
|
|
383
|
+
subject=subject,
|
|
384
|
+
message=message,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if success:
|
|
388
|
+
logger.info(
|
|
389
|
+
f"User intervention notification sent to {to_email}"
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
logger.debug(
|
|
393
|
+
f"{self.name}: Failed to send user intervention notification"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.debug(f"Failed to send user intervention notification: {e}")
|
|
398
|
+
# Don't fail authentication if notification fails
|
|
399
|
+
|
|
400
|
+
async def _notify_authentication_success_async(self) -> None:
|
|
401
|
+
"""Send email notification that OpenAthens authentication was successful."""
|
|
402
|
+
try:
|
|
403
|
+
from ..utils._email import send_email_async
|
|
404
|
+
|
|
405
|
+
# Get notification email addresses from config
|
|
406
|
+
to_email = self.scholar_config.resolve(
|
|
407
|
+
"notification_email", None, None, str
|
|
408
|
+
)
|
|
409
|
+
from_email = self.scholar_config.resolve(
|
|
410
|
+
"notification_from_email", None, "agent@scitex.ai", str
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if not to_email:
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
expiry_info = self.session_manager.format_expiry_info()
|
|
417
|
+
|
|
418
|
+
subject = "SciTeX Scholar: OpenAthens Authentication Successful"
|
|
419
|
+
message = f"""
|
|
420
|
+
OpenAthens Authentication Complete
|
|
421
|
+
|
|
422
|
+
Your OpenAthens authentication has been completed successfully.
|
|
423
|
+
|
|
424
|
+
Details:
|
|
425
|
+
- System: SciTeX Scholar Module
|
|
426
|
+
- Service: OpenAthens Single Sign-On
|
|
427
|
+
- Account: {self.email or 'Not specified'}
|
|
428
|
+
- Session expires: {expiry_info}
|
|
429
|
+
- Verification URL: {self.VERIFICATION_URL}
|
|
430
|
+
|
|
431
|
+
Status: Authenticated ✓
|
|
432
|
+
|
|
433
|
+
You can now access institutional resources through SciTeX Scholar.
|
|
434
|
+
The system will use this session for automatic PDF downloads and research access.
|
|
435
|
+
|
|
436
|
+
This is an automated notification from the SciTeX Scholar authentication system.
|
|
437
|
+
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
438
|
+
""".strip()
|
|
439
|
+
|
|
440
|
+
await send_email_async(
|
|
441
|
+
from_email=from_email,
|
|
442
|
+
to_email=to_email,
|
|
443
|
+
subject=subject,
|
|
444
|
+
message=message,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
logger.info(
|
|
448
|
+
f"Authentication success notification sent to {to_email}"
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
except Exception as e:
|
|
452
|
+
logger.debug(
|
|
453
|
+
f"Failed to send authentication success notification: {e}"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def _notify_authentication_failed_async(
|
|
457
|
+
self, error_details: str
|
|
458
|
+
) -> None:
|
|
459
|
+
"""Send email notification that OpenAthens authentication failed."""
|
|
460
|
+
try:
|
|
461
|
+
from ..utils._email import send_email_async
|
|
462
|
+
|
|
463
|
+
# Get notification email addresses from config
|
|
464
|
+
to_email = self.scholar_config.resolve(
|
|
465
|
+
"notification_email", None, None, str
|
|
466
|
+
)
|
|
467
|
+
from_email = self.scholar_config.resolve(
|
|
468
|
+
"notification_from_email", None, "agent@scitex.ai", str
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if not to_email:
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
subject = "SciTeX Scholar: OpenAthens Authentication Failed"
|
|
475
|
+
message = f"""
|
|
476
|
+
OpenAthens Authentication Failed
|
|
477
|
+
|
|
478
|
+
The OpenAthens authentication process was not completed successfully.
|
|
479
|
+
|
|
480
|
+
Details:
|
|
481
|
+
- System: SciTeX Scholar Module
|
|
482
|
+
- Service: OpenAthens Single Sign-On
|
|
483
|
+
- Account: {self.email or 'Not specified'}
|
|
484
|
+
- Error: {error_details}
|
|
485
|
+
- Timeout: {self.timeout} seconds
|
|
486
|
+
|
|
487
|
+
Status: Authentication failed ✗
|
|
488
|
+
|
|
489
|
+
Next Steps:
|
|
490
|
+
1. Check your internet connection
|
|
491
|
+
2. Verify your institutional email and credentials
|
|
492
|
+
3. Try the authentication process again
|
|
493
|
+
4. Contact your institution's IT support if problems persist
|
|
494
|
+
|
|
495
|
+
Manual login URL: {self.MYATHENS_URL}
|
|
496
|
+
|
|
497
|
+
This is an automated notification from the SciTeX Scholar authentication system.
|
|
498
|
+
Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
499
|
+
""".strip()
|
|
500
|
+
|
|
501
|
+
await send_email_async(
|
|
502
|
+
from_email=from_email,
|
|
503
|
+
to_email=to_email,
|
|
504
|
+
subject=subject,
|
|
505
|
+
message=message,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
logger.info(
|
|
509
|
+
f"Authentication failure notification sent to {to_email}"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
except Exception as e:
|
|
513
|
+
logger.debug(
|
|
514
|
+
f"Failed to send authentication failure notification: {e}"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
if __name__ == "__main__":
|
|
519
|
+
import asyncio
|
|
520
|
+
|
|
521
|
+
async def main():
|
|
522
|
+
import argparse
|
|
523
|
+
|
|
524
|
+
parser = argparse.ArgumentParser(
|
|
525
|
+
description="OpenAthens authenticator with automatic SSO automation"
|
|
526
|
+
)
|
|
527
|
+
parser.add_argument(
|
|
528
|
+
"--force", action="store_true", help="Force re-authentication"
|
|
529
|
+
)
|
|
530
|
+
parser.add_argument(
|
|
531
|
+
"--manual",
|
|
532
|
+
action="store_true",
|
|
533
|
+
help="Skip automation and require manual intervention",
|
|
534
|
+
)
|
|
535
|
+
parser.add_argument(
|
|
536
|
+
"--email", type=str, help="Override institutional email"
|
|
537
|
+
)
|
|
538
|
+
args = parser.parse_args()
|
|
539
|
+
|
|
540
|
+
# Create authenticator
|
|
541
|
+
auth = OpenAthensAuthenticator(email=args.email)
|
|
542
|
+
|
|
543
|
+
# Check if already authenticated (unless forced)
|
|
544
|
+
if not args.force:
|
|
545
|
+
try:
|
|
546
|
+
is_authenticated = await auth.is_authenticated_async()
|
|
547
|
+
if is_authenticated:
|
|
548
|
+
logger.info(
|
|
549
|
+
"Already authenticated! Using cached session."
|
|
550
|
+
)
|
|
551
|
+
session_info = await auth.get_session_info_async()
|
|
552
|
+
logger.info(f"{self.name}: Current session details:")
|
|
553
|
+
for key, value in session_info.items():
|
|
554
|
+
if key != "cookies": # Don't log sensitive data
|
|
555
|
+
logger.info(f" {key}: {value}")
|
|
556
|
+
return 0
|
|
557
|
+
else:
|
|
558
|
+
logger.info(
|
|
559
|
+
"No valid session found, proceeding with authentication..."
|
|
560
|
+
)
|
|
561
|
+
except Exception as e:
|
|
562
|
+
logger.debug(f"Session check failed: {e}")
|
|
563
|
+
logger.info(f"{self.name}: Proceeding with authentication...")
|
|
564
|
+
|
|
565
|
+
if args.manual:
|
|
566
|
+
logger.info(
|
|
567
|
+
f"{self.name}: Manual mode requested - automation will be skipped"
|
|
568
|
+
)
|
|
569
|
+
# Temporarily disable automation by removing SSO automator
|
|
570
|
+
auth.browser_authenticator.sso_automator = None
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
# Always attempt full authentication flow with SSO automation
|
|
574
|
+
result = await auth.authenticate_async(force=args.force)
|
|
575
|
+
|
|
576
|
+
if result:
|
|
577
|
+
logger.info(
|
|
578
|
+
f"{self.name}: Authentication completed successfully!"
|
|
579
|
+
)
|
|
580
|
+
logger.info(f"{self.name}: Session details:")
|
|
581
|
+
session_info = await auth.get_session_info_async()
|
|
582
|
+
for key, value in session_info.items():
|
|
583
|
+
if key != "cookies": # Don't log sensitive cookie data
|
|
584
|
+
logger.info(f" {key}: {value}")
|
|
585
|
+
|
|
586
|
+
# Show available cookies count
|
|
587
|
+
if "cookies" in result:
|
|
588
|
+
logger.info(f" cookies_count: {len(result['cookies'])}")
|
|
589
|
+
else:
|
|
590
|
+
logger.error(f"{self.name}: Authentication failed")
|
|
591
|
+
|
|
592
|
+
except Exception as e:
|
|
593
|
+
logger.error(f"Authentication error: {e}")
|
|
594
|
+
|
|
595
|
+
# Show helpful information for debugging
|
|
596
|
+
logger.info(f"{self.name}: Troubleshooting tips:")
|
|
597
|
+
logger.info(
|
|
598
|
+
f"{self.name}: 1. Check your institutional email configuration"
|
|
599
|
+
)
|
|
600
|
+
logger.info(
|
|
601
|
+
f"{self.name}: 2. Verify your institution has OpenAthens access"
|
|
602
|
+
)
|
|
603
|
+
logger.info(
|
|
604
|
+
f"{self.name}: 3. Try with --manual flag for manual authentication"
|
|
605
|
+
)
|
|
606
|
+
logger.info(
|
|
607
|
+
f"{self.name}: 4. Try with --force flag to ignore cache"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
return 1
|
|
611
|
+
|
|
612
|
+
return 0
|
|
613
|
+
|
|
614
|
+
exit_code = asyncio.run(main())
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# python -m scitex.scholar.auth._OpenAthensAuthenticator
|
|
618
|
+
|
|
619
|
+
# EOF
|