genesis-flow 1.0.0__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.
- genesis_flow-1.0.0.dist-info/METADATA +822 -0
- genesis_flow-1.0.0.dist-info/RECORD +645 -0
- genesis_flow-1.0.0.dist-info/WHEEL +5 -0
- genesis_flow-1.0.0.dist-info/entry_points.txt +19 -0
- genesis_flow-1.0.0.dist-info/licenses/LICENSE.txt +202 -0
- genesis_flow-1.0.0.dist-info/top_level.txt +1 -0
- mlflow/__init__.py +367 -0
- mlflow/__main__.py +3 -0
- mlflow/ag2/__init__.py +56 -0
- mlflow/ag2/ag2_logger.py +294 -0
- mlflow/anthropic/__init__.py +40 -0
- mlflow/anthropic/autolog.py +129 -0
- mlflow/anthropic/chat.py +144 -0
- mlflow/artifacts/__init__.py +268 -0
- mlflow/autogen/__init__.py +144 -0
- mlflow/autogen/chat.py +142 -0
- mlflow/azure/__init__.py +26 -0
- mlflow/azure/auth_handler.py +257 -0
- mlflow/azure/client.py +319 -0
- mlflow/azure/config.py +120 -0
- mlflow/azure/connection_factory.py +340 -0
- mlflow/azure/exceptions.py +27 -0
- mlflow/azure/stores.py +327 -0
- mlflow/azure/utils.py +183 -0
- mlflow/bedrock/__init__.py +45 -0
- mlflow/bedrock/_autolog.py +202 -0
- mlflow/bedrock/chat.py +122 -0
- mlflow/bedrock/stream.py +160 -0
- mlflow/bedrock/utils.py +43 -0
- mlflow/cli.py +707 -0
- mlflow/client.py +12 -0
- mlflow/config/__init__.py +56 -0
- mlflow/crewai/__init__.py +79 -0
- mlflow/crewai/autolog.py +253 -0
- mlflow/crewai/chat.py +29 -0
- mlflow/data/__init__.py +75 -0
- mlflow/data/artifact_dataset_sources.py +170 -0
- mlflow/data/code_dataset_source.py +40 -0
- mlflow/data/dataset.py +123 -0
- mlflow/data/dataset_registry.py +168 -0
- mlflow/data/dataset_source.py +110 -0
- mlflow/data/dataset_source_registry.py +219 -0
- mlflow/data/delta_dataset_source.py +167 -0
- mlflow/data/digest_utils.py +108 -0
- mlflow/data/evaluation_dataset.py +562 -0
- mlflow/data/filesystem_dataset_source.py +81 -0
- mlflow/data/http_dataset_source.py +145 -0
- mlflow/data/huggingface_dataset.py +258 -0
- mlflow/data/huggingface_dataset_source.py +118 -0
- mlflow/data/meta_dataset.py +104 -0
- mlflow/data/numpy_dataset.py +223 -0
- mlflow/data/pandas_dataset.py +231 -0
- mlflow/data/polars_dataset.py +352 -0
- mlflow/data/pyfunc_dataset_mixin.py +31 -0
- mlflow/data/schema.py +76 -0
- mlflow/data/sources.py +1 -0
- mlflow/data/spark_dataset.py +406 -0
- mlflow/data/spark_dataset_source.py +74 -0
- mlflow/data/spark_delta_utils.py +118 -0
- mlflow/data/tensorflow_dataset.py +350 -0
- mlflow/data/uc_volume_dataset_source.py +81 -0
- mlflow/db.py +27 -0
- mlflow/dspy/__init__.py +17 -0
- mlflow/dspy/autolog.py +197 -0
- mlflow/dspy/callback.py +398 -0
- mlflow/dspy/constant.py +1 -0
- mlflow/dspy/load.py +93 -0
- mlflow/dspy/save.py +393 -0
- mlflow/dspy/util.py +109 -0
- mlflow/dspy/wrapper.py +226 -0
- mlflow/entities/__init__.py +104 -0
- mlflow/entities/_mlflow_object.py +52 -0
- mlflow/entities/assessment.py +545 -0
- mlflow/entities/assessment_error.py +80 -0
- mlflow/entities/assessment_source.py +141 -0
- mlflow/entities/dataset.py +92 -0
- mlflow/entities/dataset_input.py +51 -0
- mlflow/entities/dataset_summary.py +62 -0
- mlflow/entities/document.py +48 -0
- mlflow/entities/experiment.py +109 -0
- mlflow/entities/experiment_tag.py +35 -0
- mlflow/entities/file_info.py +45 -0
- mlflow/entities/input_tag.py +35 -0
- mlflow/entities/lifecycle_stage.py +35 -0
- mlflow/entities/logged_model.py +228 -0
- mlflow/entities/logged_model_input.py +26 -0
- mlflow/entities/logged_model_output.py +32 -0
- mlflow/entities/logged_model_parameter.py +46 -0
- mlflow/entities/logged_model_status.py +74 -0
- mlflow/entities/logged_model_tag.py +33 -0
- mlflow/entities/metric.py +200 -0
- mlflow/entities/model_registry/__init__.py +29 -0
- mlflow/entities/model_registry/_model_registry_entity.py +13 -0
- mlflow/entities/model_registry/model_version.py +243 -0
- mlflow/entities/model_registry/model_version_deployment_job_run_state.py +44 -0
- mlflow/entities/model_registry/model_version_deployment_job_state.py +70 -0
- mlflow/entities/model_registry/model_version_search.py +25 -0
- mlflow/entities/model_registry/model_version_stages.py +25 -0
- mlflow/entities/model_registry/model_version_status.py +35 -0
- mlflow/entities/model_registry/model_version_tag.py +35 -0
- mlflow/entities/model_registry/prompt.py +73 -0
- mlflow/entities/model_registry/prompt_version.py +244 -0
- mlflow/entities/model_registry/registered_model.py +175 -0
- mlflow/entities/model_registry/registered_model_alias.py +35 -0
- mlflow/entities/model_registry/registered_model_deployment_job_state.py +39 -0
- mlflow/entities/model_registry/registered_model_search.py +25 -0
- mlflow/entities/model_registry/registered_model_tag.py +35 -0
- mlflow/entities/multipart_upload.py +74 -0
- mlflow/entities/param.py +49 -0
- mlflow/entities/run.py +97 -0
- mlflow/entities/run_data.py +84 -0
- mlflow/entities/run_info.py +188 -0
- mlflow/entities/run_inputs.py +59 -0
- mlflow/entities/run_outputs.py +43 -0
- mlflow/entities/run_status.py +41 -0
- mlflow/entities/run_tag.py +36 -0
- mlflow/entities/source_type.py +31 -0
- mlflow/entities/span.py +774 -0
- mlflow/entities/span_event.py +96 -0
- mlflow/entities/span_status.py +102 -0
- mlflow/entities/trace.py +317 -0
- mlflow/entities/trace_data.py +71 -0
- mlflow/entities/trace_info.py +220 -0
- mlflow/entities/trace_info_v2.py +162 -0
- mlflow/entities/trace_location.py +173 -0
- mlflow/entities/trace_state.py +39 -0
- mlflow/entities/trace_status.py +68 -0
- mlflow/entities/view_type.py +51 -0
- mlflow/environment_variables.py +866 -0
- mlflow/evaluation/__init__.py +16 -0
- mlflow/evaluation/assessment.py +369 -0
- mlflow/evaluation/evaluation.py +411 -0
- mlflow/evaluation/evaluation_tag.py +61 -0
- mlflow/evaluation/fluent.py +48 -0
- mlflow/evaluation/utils.py +201 -0
- mlflow/exceptions.py +213 -0
- mlflow/experiments.py +140 -0
- mlflow/gemini/__init__.py +81 -0
- mlflow/gemini/autolog.py +186 -0
- mlflow/gemini/chat.py +261 -0
- mlflow/genai/__init__.py +71 -0
- mlflow/genai/datasets/__init__.py +67 -0
- mlflow/genai/datasets/evaluation_dataset.py +131 -0
- mlflow/genai/evaluation/__init__.py +3 -0
- mlflow/genai/evaluation/base.py +411 -0
- mlflow/genai/evaluation/constant.py +23 -0
- mlflow/genai/evaluation/utils.py +244 -0
- mlflow/genai/judges/__init__.py +21 -0
- mlflow/genai/judges/databricks.py +404 -0
- mlflow/genai/label_schemas/__init__.py +153 -0
- mlflow/genai/label_schemas/label_schemas.py +209 -0
- mlflow/genai/labeling/__init__.py +159 -0
- mlflow/genai/labeling/labeling.py +250 -0
- mlflow/genai/optimize/__init__.py +13 -0
- mlflow/genai/optimize/base.py +198 -0
- mlflow/genai/optimize/optimizers/__init__.py +4 -0
- mlflow/genai/optimize/optimizers/base_optimizer.py +38 -0
- mlflow/genai/optimize/optimizers/dspy_mipro_optimizer.py +221 -0
- mlflow/genai/optimize/optimizers/dspy_optimizer.py +91 -0
- mlflow/genai/optimize/optimizers/utils/dspy_mipro_callback.py +76 -0
- mlflow/genai/optimize/optimizers/utils/dspy_mipro_utils.py +18 -0
- mlflow/genai/optimize/types.py +75 -0
- mlflow/genai/optimize/util.py +30 -0
- mlflow/genai/prompts/__init__.py +206 -0
- mlflow/genai/scheduled_scorers.py +431 -0
- mlflow/genai/scorers/__init__.py +26 -0
- mlflow/genai/scorers/base.py +492 -0
- mlflow/genai/scorers/builtin_scorers.py +765 -0
- mlflow/genai/scorers/scorer_utils.py +138 -0
- mlflow/genai/scorers/validation.py +165 -0
- mlflow/genai/utils/data_validation.py +146 -0
- mlflow/genai/utils/enum_utils.py +23 -0
- mlflow/genai/utils/trace_utils.py +211 -0
- mlflow/groq/__init__.py +42 -0
- mlflow/groq/_groq_autolog.py +74 -0
- mlflow/johnsnowlabs/__init__.py +888 -0
- mlflow/langchain/__init__.py +24 -0
- mlflow/langchain/api_request_parallel_processor.py +330 -0
- mlflow/langchain/autolog.py +147 -0
- mlflow/langchain/chat_agent_langgraph.py +340 -0
- mlflow/langchain/constant.py +1 -0
- mlflow/langchain/constants.py +1 -0
- mlflow/langchain/databricks_dependencies.py +444 -0
- mlflow/langchain/langchain_tracer.py +597 -0
- mlflow/langchain/model.py +919 -0
- mlflow/langchain/output_parsers.py +142 -0
- mlflow/langchain/retriever_chain.py +153 -0
- mlflow/langchain/runnables.py +527 -0
- mlflow/langchain/utils/chat.py +402 -0
- mlflow/langchain/utils/logging.py +671 -0
- mlflow/langchain/utils/serialization.py +36 -0
- mlflow/legacy_databricks_cli/__init__.py +0 -0
- mlflow/legacy_databricks_cli/configure/__init__.py +0 -0
- mlflow/legacy_databricks_cli/configure/provider.py +482 -0
- mlflow/litellm/__init__.py +175 -0
- mlflow/llama_index/__init__.py +22 -0
- mlflow/llama_index/autolog.py +55 -0
- mlflow/llama_index/chat.py +43 -0
- mlflow/llama_index/constant.py +1 -0
- mlflow/llama_index/model.py +577 -0
- mlflow/llama_index/pyfunc_wrapper.py +332 -0
- mlflow/llama_index/serialize_objects.py +188 -0
- mlflow/llama_index/tracer.py +561 -0
- mlflow/metrics/__init__.py +479 -0
- mlflow/metrics/base.py +39 -0
- mlflow/metrics/genai/__init__.py +25 -0
- mlflow/metrics/genai/base.py +101 -0
- mlflow/metrics/genai/genai_metric.py +771 -0
- mlflow/metrics/genai/metric_definitions.py +450 -0
- mlflow/metrics/genai/model_utils.py +371 -0
- mlflow/metrics/genai/prompt_template.py +68 -0
- mlflow/metrics/genai/prompts/__init__.py +0 -0
- mlflow/metrics/genai/prompts/v1.py +422 -0
- mlflow/metrics/genai/utils.py +6 -0
- mlflow/metrics/metric_definitions.py +619 -0
- mlflow/mismatch.py +34 -0
- mlflow/mistral/__init__.py +34 -0
- mlflow/mistral/autolog.py +71 -0
- mlflow/mistral/chat.py +135 -0
- mlflow/ml_package_versions.py +452 -0
- mlflow/models/__init__.py +97 -0
- mlflow/models/auth_policy.py +83 -0
- mlflow/models/cli.py +354 -0
- mlflow/models/container/__init__.py +294 -0
- mlflow/models/container/scoring_server/__init__.py +0 -0
- mlflow/models/container/scoring_server/nginx.conf +39 -0
- mlflow/models/dependencies_schemas.py +287 -0
- mlflow/models/display_utils.py +158 -0
- mlflow/models/docker_utils.py +211 -0
- mlflow/models/evaluation/__init__.py +23 -0
- mlflow/models/evaluation/_shap_patch.py +64 -0
- mlflow/models/evaluation/artifacts.py +194 -0
- mlflow/models/evaluation/base.py +1811 -0
- mlflow/models/evaluation/calibration_curve.py +109 -0
- mlflow/models/evaluation/default_evaluator.py +996 -0
- mlflow/models/evaluation/deprecated.py +23 -0
- mlflow/models/evaluation/evaluator_registry.py +80 -0
- mlflow/models/evaluation/evaluators/classifier.py +704 -0
- mlflow/models/evaluation/evaluators/default.py +233 -0
- mlflow/models/evaluation/evaluators/regressor.py +96 -0
- mlflow/models/evaluation/evaluators/shap.py +296 -0
- mlflow/models/evaluation/lift_curve.py +178 -0
- mlflow/models/evaluation/utils/metric.py +123 -0
- mlflow/models/evaluation/utils/trace.py +179 -0
- mlflow/models/evaluation/validation.py +434 -0
- mlflow/models/flavor_backend.py +93 -0
- mlflow/models/flavor_backend_registry.py +53 -0
- mlflow/models/model.py +1639 -0
- mlflow/models/model_config.py +150 -0
- mlflow/models/notebook_resources/agent_evaluation_template.html +235 -0
- mlflow/models/notebook_resources/eval_with_dataset_example.py +22 -0
- mlflow/models/notebook_resources/eval_with_synthetic_example.py +22 -0
- mlflow/models/python_api.py +369 -0
- mlflow/models/rag_signatures.py +128 -0
- mlflow/models/resources.py +321 -0
- mlflow/models/signature.py +662 -0
- mlflow/models/utils.py +2054 -0
- mlflow/models/wheeled_model.py +280 -0
- mlflow/openai/__init__.py +57 -0
- mlflow/openai/_agent_tracer.py +364 -0
- mlflow/openai/api_request_parallel_processor.py +131 -0
- mlflow/openai/autolog.py +509 -0
- mlflow/openai/constant.py +1 -0
- mlflow/openai/model.py +824 -0
- mlflow/openai/utils/chat_schema.py +367 -0
- mlflow/optuna/__init__.py +3 -0
- mlflow/optuna/storage.py +646 -0
- mlflow/plugins/__init__.py +72 -0
- mlflow/plugins/base.py +358 -0
- mlflow/plugins/builtin/__init__.py +24 -0
- mlflow/plugins/builtin/pytorch_plugin.py +150 -0
- mlflow/plugins/builtin/sklearn_plugin.py +158 -0
- mlflow/plugins/builtin/transformers_plugin.py +187 -0
- mlflow/plugins/cli.py +321 -0
- mlflow/plugins/discovery.py +340 -0
- mlflow/plugins/manager.py +465 -0
- mlflow/plugins/registry.py +316 -0
- mlflow/plugins/templates/framework_plugin_template.py +329 -0
- mlflow/prompt/constants.py +20 -0
- mlflow/prompt/promptlab_model.py +197 -0
- mlflow/prompt/registry_utils.py +248 -0
- mlflow/promptflow/__init__.py +495 -0
- mlflow/protos/__init__.py +0 -0
- mlflow/protos/assessments_pb2.py +174 -0
- mlflow/protos/databricks_artifacts_pb2.py +489 -0
- mlflow/protos/databricks_filesystem_service_pb2.py +196 -0
- mlflow/protos/databricks_managed_catalog_messages_pb2.py +95 -0
- mlflow/protos/databricks_managed_catalog_service_pb2.py +86 -0
- mlflow/protos/databricks_pb2.py +267 -0
- mlflow/protos/databricks_trace_server_pb2.py +374 -0
- mlflow/protos/databricks_uc_registry_messages_pb2.py +1249 -0
- mlflow/protos/databricks_uc_registry_service_pb2.py +170 -0
- mlflow/protos/facet_feature_statistics_pb2.py +296 -0
- mlflow/protos/internal_pb2.py +77 -0
- mlflow/protos/mlflow_artifacts_pb2.py +336 -0
- mlflow/protos/model_registry_pb2.py +1073 -0
- mlflow/protos/scalapb/__init__.py +0 -0
- mlflow/protos/scalapb/scalapb_pb2.py +104 -0
- mlflow/protos/service_pb2.py +2600 -0
- mlflow/protos/unity_catalog_oss_messages_pb2.py +457 -0
- mlflow/protos/unity_catalog_oss_service_pb2.py +130 -0
- mlflow/protos/unity_catalog_prompt_messages_pb2.py +447 -0
- mlflow/protos/unity_catalog_prompt_messages_pb2_grpc.py +24 -0
- mlflow/protos/unity_catalog_prompt_service_pb2.py +164 -0
- mlflow/protos/unity_catalog_prompt_service_pb2_grpc.py +785 -0
- mlflow/py.typed +0 -0
- mlflow/pydantic_ai/__init__.py +57 -0
- mlflow/pydantic_ai/autolog.py +173 -0
- mlflow/pyfunc/__init__.py +3844 -0
- mlflow/pyfunc/_mlflow_pyfunc_backend_predict.py +61 -0
- mlflow/pyfunc/backend.py +523 -0
- mlflow/pyfunc/context.py +78 -0
- mlflow/pyfunc/dbconnect_artifact_cache.py +144 -0
- mlflow/pyfunc/loaders/__init__.py +7 -0
- mlflow/pyfunc/loaders/chat_agent.py +117 -0
- mlflow/pyfunc/loaders/chat_model.py +125 -0
- mlflow/pyfunc/loaders/code_model.py +31 -0
- mlflow/pyfunc/loaders/responses_agent.py +112 -0
- mlflow/pyfunc/mlserver.py +46 -0
- mlflow/pyfunc/model.py +1473 -0
- mlflow/pyfunc/scoring_server/__init__.py +604 -0
- mlflow/pyfunc/scoring_server/app.py +7 -0
- mlflow/pyfunc/scoring_server/client.py +146 -0
- mlflow/pyfunc/spark_model_cache.py +48 -0
- mlflow/pyfunc/stdin_server.py +44 -0
- mlflow/pyfunc/utils/__init__.py +3 -0
- mlflow/pyfunc/utils/data_validation.py +224 -0
- mlflow/pyfunc/utils/environment.py +22 -0
- mlflow/pyfunc/utils/input_converter.py +47 -0
- mlflow/pyfunc/utils/serving_data_parser.py +11 -0
- mlflow/pytorch/__init__.py +1171 -0
- mlflow/pytorch/_lightning_autolog.py +580 -0
- mlflow/pytorch/_pytorch_autolog.py +50 -0
- mlflow/pytorch/pickle_module.py +35 -0
- mlflow/rfunc/__init__.py +42 -0
- mlflow/rfunc/backend.py +134 -0
- mlflow/runs.py +89 -0
- mlflow/server/__init__.py +302 -0
- mlflow/server/auth/__init__.py +1224 -0
- mlflow/server/auth/__main__.py +4 -0
- mlflow/server/auth/basic_auth.ini +6 -0
- mlflow/server/auth/cli.py +11 -0
- mlflow/server/auth/client.py +537 -0
- mlflow/server/auth/config.py +34 -0
- mlflow/server/auth/db/__init__.py +0 -0
- mlflow/server/auth/db/cli.py +18 -0
- mlflow/server/auth/db/migrations/__init__.py +0 -0
- mlflow/server/auth/db/migrations/alembic.ini +110 -0
- mlflow/server/auth/db/migrations/env.py +76 -0
- mlflow/server/auth/db/migrations/versions/8606fa83a998_initial_migration.py +51 -0
- mlflow/server/auth/db/migrations/versions/__init__.py +0 -0
- mlflow/server/auth/db/models.py +67 -0
- mlflow/server/auth/db/utils.py +37 -0
- mlflow/server/auth/entities.py +165 -0
- mlflow/server/auth/logo.py +14 -0
- mlflow/server/auth/permissions.py +65 -0
- mlflow/server/auth/routes.py +18 -0
- mlflow/server/auth/sqlalchemy_store.py +263 -0
- mlflow/server/graphql/__init__.py +0 -0
- mlflow/server/graphql/autogenerated_graphql_schema.py +353 -0
- mlflow/server/graphql/graphql_custom_scalars.py +24 -0
- mlflow/server/graphql/graphql_errors.py +15 -0
- mlflow/server/graphql/graphql_no_batching.py +89 -0
- mlflow/server/graphql/graphql_schema_extensions.py +74 -0
- mlflow/server/handlers.py +3217 -0
- mlflow/server/prometheus_exporter.py +17 -0
- mlflow/server/validation.py +30 -0
- mlflow/shap/__init__.py +691 -0
- mlflow/sklearn/__init__.py +1994 -0
- mlflow/sklearn/utils.py +1041 -0
- mlflow/smolagents/__init__.py +66 -0
- mlflow/smolagents/autolog.py +139 -0
- mlflow/smolagents/chat.py +29 -0
- mlflow/store/__init__.py +10 -0
- mlflow/store/_unity_catalog/__init__.py +1 -0
- mlflow/store/_unity_catalog/lineage/__init__.py +1 -0
- mlflow/store/_unity_catalog/lineage/constants.py +2 -0
- mlflow/store/_unity_catalog/registry/__init__.py +6 -0
- mlflow/store/_unity_catalog/registry/prompt_info.py +75 -0
- mlflow/store/_unity_catalog/registry/rest_store.py +1740 -0
- mlflow/store/_unity_catalog/registry/uc_oss_rest_store.py +507 -0
- mlflow/store/_unity_catalog/registry/utils.py +121 -0
- mlflow/store/artifact/__init__.py +0 -0
- mlflow/store/artifact/artifact_repo.py +472 -0
- mlflow/store/artifact/artifact_repository_registry.py +154 -0
- mlflow/store/artifact/azure_blob_artifact_repo.py +275 -0
- mlflow/store/artifact/azure_data_lake_artifact_repo.py +295 -0
- mlflow/store/artifact/cli.py +141 -0
- mlflow/store/artifact/cloud_artifact_repo.py +332 -0
- mlflow/store/artifact/databricks_artifact_repo.py +729 -0
- mlflow/store/artifact/databricks_artifact_repo_resources.py +301 -0
- mlflow/store/artifact/databricks_logged_model_artifact_repo.py +93 -0
- mlflow/store/artifact/databricks_models_artifact_repo.py +216 -0
- mlflow/store/artifact/databricks_sdk_artifact_repo.py +134 -0
- mlflow/store/artifact/databricks_sdk_models_artifact_repo.py +97 -0
- mlflow/store/artifact/dbfs_artifact_repo.py +240 -0
- mlflow/store/artifact/ftp_artifact_repo.py +132 -0
- mlflow/store/artifact/gcs_artifact_repo.py +296 -0
- mlflow/store/artifact/hdfs_artifact_repo.py +209 -0
- mlflow/store/artifact/http_artifact_repo.py +218 -0
- mlflow/store/artifact/local_artifact_repo.py +142 -0
- mlflow/store/artifact/mlflow_artifacts_repo.py +94 -0
- mlflow/store/artifact/models_artifact_repo.py +259 -0
- mlflow/store/artifact/optimized_s3_artifact_repo.py +356 -0
- mlflow/store/artifact/presigned_url_artifact_repo.py +173 -0
- mlflow/store/artifact/r2_artifact_repo.py +70 -0
- mlflow/store/artifact/runs_artifact_repo.py +265 -0
- mlflow/store/artifact/s3_artifact_repo.py +330 -0
- mlflow/store/artifact/sftp_artifact_repo.py +141 -0
- mlflow/store/artifact/uc_volume_artifact_repo.py +76 -0
- mlflow/store/artifact/unity_catalog_models_artifact_repo.py +168 -0
- mlflow/store/artifact/unity_catalog_oss_models_artifact_repo.py +168 -0
- mlflow/store/artifact/utils/__init__.py +0 -0
- mlflow/store/artifact/utils/models.py +148 -0
- mlflow/store/db/__init__.py +0 -0
- mlflow/store/db/base_sql_model.py +3 -0
- mlflow/store/db/db_types.py +10 -0
- mlflow/store/db/utils.py +314 -0
- mlflow/store/db_migrations/__init__.py +0 -0
- mlflow/store/db_migrations/alembic.ini +74 -0
- mlflow/store/db_migrations/env.py +84 -0
- mlflow/store/db_migrations/versions/0584bdc529eb_add_cascading_deletion_to_datasets_from_experiments.py +88 -0
- mlflow/store/db_migrations/versions/0a8213491aaa_drop_duplicate_killed_constraint.py +49 -0
- mlflow/store/db_migrations/versions/0c779009ac13_add_deleted_time_field_to_runs_table.py +24 -0
- mlflow/store/db_migrations/versions/181f10493468_allow_nulls_for_metric_values.py +35 -0
- mlflow/store/db_migrations/versions/27a6a02d2cf1_add_model_version_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/2b4d017a5e9b_add_model_registry_tables_to_db.py +77 -0
- mlflow/store/db_migrations/versions/2d6e25af4d3e_increase_max_param_val_length.py +33 -0
- mlflow/store/db_migrations/versions/3500859a5d39_add_model_aliases_table.py +50 -0
- mlflow/store/db_migrations/versions/39d1c3be5f05_add_is_nan_constraint_for_metrics_tables_if_necessary.py +41 -0
- mlflow/store/db_migrations/versions/400f98739977_add_logged_model_tables.py +123 -0
- mlflow/store/db_migrations/versions/4465047574b1_increase_max_dataset_schema_size.py +38 -0
- mlflow/store/db_migrations/versions/451aebb31d03_add_metric_step.py +35 -0
- mlflow/store/db_migrations/versions/5b0e9adcef9c_add_cascade_deletion_to_trace_tables_fk.py +40 -0
- mlflow/store/db_migrations/versions/6953534de441_add_step_to_inputs_table.py +25 -0
- mlflow/store/db_migrations/versions/728d730b5ebd_add_registered_model_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/7ac759974ad8_update_run_tags_with_larger_limit.py +36 -0
- mlflow/store/db_migrations/versions/7f2a7d5fae7d_add_datasets_inputs_input_tags_tables.py +82 -0
- mlflow/store/db_migrations/versions/84291f40a231_add_run_link_to_model_version.py +26 -0
- mlflow/store/db_migrations/versions/867495a8f9d4_add_trace_tables.py +90 -0
- mlflow/store/db_migrations/versions/89d4b8295536_create_latest_metrics_table.py +169 -0
- mlflow/store/db_migrations/versions/90e64c465722_migrate_user_column_to_tags.py +64 -0
- mlflow/store/db_migrations/versions/97727af70f4d_creation_time_last_update_time_experiments.py +25 -0
- mlflow/store/db_migrations/versions/__init__.py +0 -0
- mlflow/store/db_migrations/versions/a8c4a736bde6_allow_nulls_for_run_id.py +27 -0
- mlflow/store/db_migrations/versions/acf3f17fdcc7_add_storage_location_field_to_model_.py +29 -0
- mlflow/store/db_migrations/versions/bd07f7e963c5_create_index_on_run_uuid.py +26 -0
- mlflow/store/db_migrations/versions/bda7b8c39065_increase_model_version_tag_value_limit.py +38 -0
- mlflow/store/db_migrations/versions/c48cb773bb87_reset_default_value_for_is_nan_in_metrics_table_for_mysql.py +41 -0
- mlflow/store/db_migrations/versions/cbc13b556ace_add_v3_trace_schema_columns.py +31 -0
- mlflow/store/db_migrations/versions/cc1f77228345_change_param_value_length_to_500.py +34 -0
- mlflow/store/db_migrations/versions/cfd24bdc0731_update_run_status_constraint_with_killed.py +78 -0
- mlflow/store/db_migrations/versions/df50e92ffc5e_add_experiment_tags_table.py +38 -0
- mlflow/store/db_migrations/versions/f5a4f2784254_increase_run_tag_value_limit.py +36 -0
- mlflow/store/entities/__init__.py +3 -0
- mlflow/store/entities/paged_list.py +18 -0
- mlflow/store/model_registry/__init__.py +10 -0
- mlflow/store/model_registry/abstract_store.py +1081 -0
- mlflow/store/model_registry/base_rest_store.py +44 -0
- mlflow/store/model_registry/databricks_workspace_model_registry_rest_store.py +37 -0
- mlflow/store/model_registry/dbmodels/__init__.py +0 -0
- mlflow/store/model_registry/dbmodels/models.py +206 -0
- mlflow/store/model_registry/file_store.py +1091 -0
- mlflow/store/model_registry/rest_store.py +481 -0
- mlflow/store/model_registry/sqlalchemy_store.py +1286 -0
- mlflow/store/tracking/__init__.py +23 -0
- mlflow/store/tracking/abstract_store.py +816 -0
- mlflow/store/tracking/dbmodels/__init__.py +0 -0
- mlflow/store/tracking/dbmodels/initial_models.py +243 -0
- mlflow/store/tracking/dbmodels/models.py +1073 -0
- mlflow/store/tracking/file_store.py +2438 -0
- mlflow/store/tracking/postgres_managed_identity.py +146 -0
- mlflow/store/tracking/rest_store.py +1131 -0
- mlflow/store/tracking/sqlalchemy_store.py +2785 -0
- mlflow/system_metrics/__init__.py +61 -0
- mlflow/system_metrics/metrics/__init__.py +0 -0
- mlflow/system_metrics/metrics/base_metrics_monitor.py +32 -0
- mlflow/system_metrics/metrics/cpu_monitor.py +23 -0
- mlflow/system_metrics/metrics/disk_monitor.py +21 -0
- mlflow/system_metrics/metrics/gpu_monitor.py +71 -0
- mlflow/system_metrics/metrics/network_monitor.py +34 -0
- mlflow/system_metrics/metrics/rocm_monitor.py +123 -0
- mlflow/system_metrics/system_metrics_monitor.py +198 -0
- mlflow/tracing/__init__.py +16 -0
- mlflow/tracing/assessment.py +356 -0
- mlflow/tracing/client.py +531 -0
- mlflow/tracing/config.py +125 -0
- mlflow/tracing/constant.py +105 -0
- mlflow/tracing/destination.py +81 -0
- mlflow/tracing/display/__init__.py +40 -0
- mlflow/tracing/display/display_handler.py +196 -0
- mlflow/tracing/export/async_export_queue.py +186 -0
- mlflow/tracing/export/inference_table.py +138 -0
- mlflow/tracing/export/mlflow_v3.py +137 -0
- mlflow/tracing/export/utils.py +70 -0
- mlflow/tracing/fluent.py +1417 -0
- mlflow/tracing/processor/base_mlflow.py +199 -0
- mlflow/tracing/processor/inference_table.py +175 -0
- mlflow/tracing/processor/mlflow_v3.py +47 -0
- mlflow/tracing/processor/otel.py +73 -0
- mlflow/tracing/provider.py +487 -0
- mlflow/tracing/trace_manager.py +200 -0
- mlflow/tracing/utils/__init__.py +616 -0
- mlflow/tracing/utils/artifact_utils.py +28 -0
- mlflow/tracing/utils/copy.py +55 -0
- mlflow/tracing/utils/environment.py +55 -0
- mlflow/tracing/utils/exception.py +21 -0
- mlflow/tracing/utils/once.py +35 -0
- mlflow/tracing/utils/otlp.py +63 -0
- mlflow/tracing/utils/processor.py +54 -0
- mlflow/tracing/utils/search.py +292 -0
- mlflow/tracing/utils/timeout.py +250 -0
- mlflow/tracing/utils/token.py +19 -0
- mlflow/tracing/utils/truncation.py +124 -0
- mlflow/tracing/utils/warning.py +76 -0
- mlflow/tracking/__init__.py +39 -0
- mlflow/tracking/_model_registry/__init__.py +1 -0
- mlflow/tracking/_model_registry/client.py +764 -0
- mlflow/tracking/_model_registry/fluent.py +853 -0
- mlflow/tracking/_model_registry/registry.py +67 -0
- mlflow/tracking/_model_registry/utils.py +251 -0
- mlflow/tracking/_tracking_service/__init__.py +0 -0
- mlflow/tracking/_tracking_service/client.py +883 -0
- mlflow/tracking/_tracking_service/registry.py +56 -0
- mlflow/tracking/_tracking_service/utils.py +275 -0
- mlflow/tracking/artifact_utils.py +179 -0
- mlflow/tracking/client.py +5900 -0
- mlflow/tracking/context/__init__.py +0 -0
- mlflow/tracking/context/abstract_context.py +35 -0
- mlflow/tracking/context/databricks_cluster_context.py +15 -0
- mlflow/tracking/context/databricks_command_context.py +15 -0
- mlflow/tracking/context/databricks_job_context.py +49 -0
- mlflow/tracking/context/databricks_notebook_context.py +41 -0
- mlflow/tracking/context/databricks_repo_context.py +43 -0
- mlflow/tracking/context/default_context.py +51 -0
- mlflow/tracking/context/git_context.py +32 -0
- mlflow/tracking/context/registry.py +98 -0
- mlflow/tracking/context/system_environment_context.py +15 -0
- mlflow/tracking/default_experiment/__init__.py +1 -0
- mlflow/tracking/default_experiment/abstract_context.py +43 -0
- mlflow/tracking/default_experiment/databricks_notebook_experiment_provider.py +44 -0
- mlflow/tracking/default_experiment/registry.py +75 -0
- mlflow/tracking/fluent.py +3595 -0
- mlflow/tracking/metric_value_conversion_utils.py +93 -0
- mlflow/tracking/multimedia.py +206 -0
- mlflow/tracking/registry.py +86 -0
- mlflow/tracking/request_auth/__init__.py +0 -0
- mlflow/tracking/request_auth/abstract_request_auth_provider.py +34 -0
- mlflow/tracking/request_auth/registry.py +60 -0
- mlflow/tracking/request_header/__init__.py +0 -0
- mlflow/tracking/request_header/abstract_request_header_provider.py +36 -0
- mlflow/tracking/request_header/databricks_request_header_provider.py +38 -0
- mlflow/tracking/request_header/default_request_header_provider.py +17 -0
- mlflow/tracking/request_header/registry.py +79 -0
- mlflow/transformers/__init__.py +2982 -0
- mlflow/transformers/flavor_config.py +258 -0
- mlflow/transformers/hub_utils.py +83 -0
- mlflow/transformers/llm_inference_utils.py +468 -0
- mlflow/transformers/model_io.py +301 -0
- mlflow/transformers/peft.py +51 -0
- mlflow/transformers/signature.py +183 -0
- mlflow/transformers/torch_utils.py +55 -0
- mlflow/types/__init__.py +21 -0
- mlflow/types/agent.py +270 -0
- mlflow/types/chat.py +240 -0
- mlflow/types/llm.py +935 -0
- mlflow/types/responses.py +139 -0
- mlflow/types/responses_helpers.py +416 -0
- mlflow/types/schema.py +1505 -0
- mlflow/types/type_hints.py +647 -0
- mlflow/types/utils.py +753 -0
- mlflow/utils/__init__.py +283 -0
- mlflow/utils/_capture_modules.py +256 -0
- mlflow/utils/_capture_transformers_modules.py +75 -0
- mlflow/utils/_spark_utils.py +201 -0
- mlflow/utils/_unity_catalog_oss_utils.py +97 -0
- mlflow/utils/_unity_catalog_utils.py +479 -0
- mlflow/utils/annotations.py +218 -0
- mlflow/utils/arguments_utils.py +16 -0
- mlflow/utils/async_logging/__init__.py +1 -0
- mlflow/utils/async_logging/async_artifacts_logging_queue.py +258 -0
- mlflow/utils/async_logging/async_logging_queue.py +366 -0
- mlflow/utils/async_logging/run_artifact.py +38 -0
- mlflow/utils/async_logging/run_batch.py +58 -0
- mlflow/utils/async_logging/run_operations.py +49 -0
- mlflow/utils/autologging_utils/__init__.py +737 -0
- mlflow/utils/autologging_utils/client.py +432 -0
- mlflow/utils/autologging_utils/config.py +33 -0
- mlflow/utils/autologging_utils/events.py +294 -0
- mlflow/utils/autologging_utils/logging_and_warnings.py +328 -0
- mlflow/utils/autologging_utils/metrics_queue.py +71 -0
- mlflow/utils/autologging_utils/safety.py +1104 -0
- mlflow/utils/autologging_utils/versioning.py +95 -0
- mlflow/utils/checkpoint_utils.py +206 -0
- mlflow/utils/class_utils.py +6 -0
- mlflow/utils/cli_args.py +257 -0
- mlflow/utils/conda.py +354 -0
- mlflow/utils/credentials.py +231 -0
- mlflow/utils/data_utils.py +17 -0
- mlflow/utils/databricks_utils.py +1436 -0
- mlflow/utils/docstring_utils.py +477 -0
- mlflow/utils/doctor.py +133 -0
- mlflow/utils/download_cloud_file_chunk.py +43 -0
- mlflow/utils/env_manager.py +16 -0
- mlflow/utils/env_pack.py +131 -0
- mlflow/utils/environment.py +1009 -0
- mlflow/utils/exception_utils.py +14 -0
- mlflow/utils/file_utils.py +978 -0
- mlflow/utils/git_utils.py +77 -0
- mlflow/utils/gorilla.py +797 -0
- mlflow/utils/import_hooks/__init__.py +363 -0
- mlflow/utils/lazy_load.py +51 -0
- mlflow/utils/logging_utils.py +168 -0
- mlflow/utils/mime_type_utils.py +58 -0
- mlflow/utils/mlflow_tags.py +103 -0
- mlflow/utils/model_utils.py +486 -0
- mlflow/utils/name_utils.py +346 -0
- mlflow/utils/nfs_on_spark.py +62 -0
- mlflow/utils/openai_utils.py +164 -0
- mlflow/utils/os.py +12 -0
- mlflow/utils/oss_registry_utils.py +29 -0
- mlflow/utils/plugins.py +17 -0
- mlflow/utils/process.py +182 -0
- mlflow/utils/promptlab_utils.py +146 -0
- mlflow/utils/proto_json_utils.py +743 -0
- mlflow/utils/pydantic_utils.py +54 -0
- mlflow/utils/request_utils.py +279 -0
- mlflow/utils/requirements_utils.py +704 -0
- mlflow/utils/rest_utils.py +673 -0
- mlflow/utils/search_logged_model_utils.py +127 -0
- mlflow/utils/search_utils.py +2111 -0
- mlflow/utils/secure_loading.py +221 -0
- mlflow/utils/security_validation.py +384 -0
- mlflow/utils/server_cli_utils.py +61 -0
- mlflow/utils/spark_utils.py +15 -0
- mlflow/utils/string_utils.py +138 -0
- mlflow/utils/thread_utils.py +63 -0
- mlflow/utils/time.py +54 -0
- mlflow/utils/timeout.py +42 -0
- mlflow/utils/uri.py +572 -0
- mlflow/utils/validation.py +662 -0
- mlflow/utils/virtualenv.py +458 -0
- mlflow/utils/warnings_utils.py +25 -0
- mlflow/utils/yaml_utils.py +179 -0
- mlflow/version.py +24 -0
@@ -0,0 +1,1224 @@
|
|
1
|
+
"""
|
2
|
+
Usage
|
3
|
+
-----
|
4
|
+
|
5
|
+
.. code-block:: bash
|
6
|
+
|
7
|
+
mlflow server --app-name basic-auth
|
8
|
+
"""
|
9
|
+
|
10
|
+
import functools
|
11
|
+
import importlib
|
12
|
+
import logging
|
13
|
+
import re
|
14
|
+
from typing import Any, Callable, Optional, Union
|
15
|
+
|
16
|
+
import sqlalchemy
|
17
|
+
from flask import (
|
18
|
+
Flask,
|
19
|
+
Request,
|
20
|
+
Response,
|
21
|
+
flash,
|
22
|
+
jsonify,
|
23
|
+
make_response,
|
24
|
+
render_template_string,
|
25
|
+
request,
|
26
|
+
)
|
27
|
+
from werkzeug.datastructures import Authorization
|
28
|
+
|
29
|
+
from mlflow import MlflowException
|
30
|
+
from mlflow.entities import Experiment
|
31
|
+
from mlflow.entities.logged_model import LoggedModel
|
32
|
+
from mlflow.entities.model_registry import RegisteredModel
|
33
|
+
from mlflow.environment_variables import MLFLOW_FLASK_SERVER_SECRET_KEY
|
34
|
+
from mlflow.protos.databricks_pb2 import (
|
35
|
+
BAD_REQUEST,
|
36
|
+
INTERNAL_ERROR,
|
37
|
+
INVALID_PARAMETER_VALUE,
|
38
|
+
RESOURCE_DOES_NOT_EXIST,
|
39
|
+
ErrorCode,
|
40
|
+
)
|
41
|
+
from mlflow.protos.model_registry_pb2 import (
|
42
|
+
CreateModelVersion,
|
43
|
+
CreateRegisteredModel,
|
44
|
+
DeleteModelVersion,
|
45
|
+
DeleteModelVersionTag,
|
46
|
+
DeleteRegisteredModel,
|
47
|
+
DeleteRegisteredModelAlias,
|
48
|
+
DeleteRegisteredModelTag,
|
49
|
+
GetLatestVersions,
|
50
|
+
GetModelVersion,
|
51
|
+
GetModelVersionByAlias,
|
52
|
+
GetModelVersionDownloadUri,
|
53
|
+
GetRegisteredModel,
|
54
|
+
RenameRegisteredModel,
|
55
|
+
SearchRegisteredModels,
|
56
|
+
SetModelVersionTag,
|
57
|
+
SetRegisteredModelAlias,
|
58
|
+
SetRegisteredModelTag,
|
59
|
+
TransitionModelVersionStage,
|
60
|
+
UpdateModelVersion,
|
61
|
+
UpdateRegisteredModel,
|
62
|
+
)
|
63
|
+
from mlflow.protos.service_pb2 import (
|
64
|
+
CreateExperiment,
|
65
|
+
# Routes for logged models
|
66
|
+
CreateLoggedModel,
|
67
|
+
CreateRun,
|
68
|
+
DeleteExperiment,
|
69
|
+
DeleteLoggedModel,
|
70
|
+
DeleteLoggedModelTag,
|
71
|
+
DeleteRun,
|
72
|
+
DeleteTag,
|
73
|
+
FinalizeLoggedModel,
|
74
|
+
GetExperiment,
|
75
|
+
GetExperimentByName,
|
76
|
+
GetLoggedModel,
|
77
|
+
GetMetricHistory,
|
78
|
+
GetRun,
|
79
|
+
ListArtifacts,
|
80
|
+
LogBatch,
|
81
|
+
LogLoggedModelParamsRequest,
|
82
|
+
LogMetric,
|
83
|
+
LogModel,
|
84
|
+
LogParam,
|
85
|
+
RestoreExperiment,
|
86
|
+
RestoreRun,
|
87
|
+
SearchExperiments,
|
88
|
+
SearchLoggedModels,
|
89
|
+
SetExperimentTag,
|
90
|
+
SetLoggedModelTags,
|
91
|
+
SetTag,
|
92
|
+
UpdateExperiment,
|
93
|
+
UpdateRun,
|
94
|
+
)
|
95
|
+
from mlflow.server import app
|
96
|
+
from mlflow.server.auth.config import read_auth_config
|
97
|
+
from mlflow.server.auth.logo import MLFLOW_LOGO
|
98
|
+
from mlflow.server.auth.permissions import MANAGE, Permission, get_permission
|
99
|
+
from mlflow.server.auth.routes import (
|
100
|
+
CREATE_EXPERIMENT_PERMISSION,
|
101
|
+
CREATE_REGISTERED_MODEL_PERMISSION,
|
102
|
+
CREATE_USER,
|
103
|
+
CREATE_USER_UI,
|
104
|
+
DELETE_EXPERIMENT_PERMISSION,
|
105
|
+
DELETE_REGISTERED_MODEL_PERMISSION,
|
106
|
+
DELETE_USER,
|
107
|
+
GET_EXPERIMENT_PERMISSION,
|
108
|
+
GET_REGISTERED_MODEL_PERMISSION,
|
109
|
+
GET_USER,
|
110
|
+
HOME,
|
111
|
+
SIGNUP,
|
112
|
+
UPDATE_EXPERIMENT_PERMISSION,
|
113
|
+
UPDATE_REGISTERED_MODEL_PERMISSION,
|
114
|
+
UPDATE_USER_ADMIN,
|
115
|
+
UPDATE_USER_PASSWORD,
|
116
|
+
)
|
117
|
+
from mlflow.server.auth.sqlalchemy_store import SqlAlchemyStore
|
118
|
+
from mlflow.server.handlers import (
|
119
|
+
_get_model_registry_store,
|
120
|
+
_get_request_message,
|
121
|
+
_get_tracking_store,
|
122
|
+
catch_mlflow_exception,
|
123
|
+
get_endpoints,
|
124
|
+
)
|
125
|
+
from mlflow.store.entities import PagedList
|
126
|
+
from mlflow.utils.proto_json_utils import message_to_json, parse_dict
|
127
|
+
from mlflow.utils.rest_utils import _REST_API_PATH_PREFIX
|
128
|
+
from mlflow.utils.search_utils import SearchUtils
|
129
|
+
|
130
|
+
try:
|
131
|
+
from flask_wtf.csrf import CSRFProtect
|
132
|
+
except ImportError as e:
|
133
|
+
raise ImportError(
|
134
|
+
"The MLflow basic auth app requires the Flask-WTF package to perform CSRF "
|
135
|
+
"validation. Please run `pip install mlflow[auth]` to install it."
|
136
|
+
) from e
|
137
|
+
|
138
|
+
_logger = logging.getLogger(__name__)
|
139
|
+
|
140
|
+
auth_config = read_auth_config()
|
141
|
+
store = SqlAlchemyStore()
|
142
|
+
|
143
|
+
|
144
|
+
def is_unprotected_route(path: str) -> bool:
|
145
|
+
return path.startswith(("/static", "/favicon.ico", "/health"))
|
146
|
+
|
147
|
+
|
148
|
+
def make_basic_auth_response() -> Response:
|
149
|
+
res = make_response(
|
150
|
+
"You are not authenticated. Please see "
|
151
|
+
"https://www.mlflow.org/docs/latest/auth/index.html#authenticating-to-mlflow "
|
152
|
+
"on how to authenticate."
|
153
|
+
)
|
154
|
+
res.status_code = 401
|
155
|
+
res.headers["WWW-Authenticate"] = 'Basic realm="mlflow"'
|
156
|
+
return res
|
157
|
+
|
158
|
+
|
159
|
+
def make_forbidden_response() -> Response:
|
160
|
+
res = make_response("Permission denied")
|
161
|
+
res.status_code = 403
|
162
|
+
return res
|
163
|
+
|
164
|
+
|
165
|
+
def _get_request_param(param: str) -> str:
|
166
|
+
if request.method == "GET":
|
167
|
+
args = request.args
|
168
|
+
elif request.method in ("POST", "PATCH"):
|
169
|
+
args = request.json
|
170
|
+
elif request.method == "DELETE":
|
171
|
+
args = request.json if request.is_json else request.args
|
172
|
+
else:
|
173
|
+
raise MlflowException(
|
174
|
+
f"Unsupported HTTP method '{request.method}'",
|
175
|
+
BAD_REQUEST,
|
176
|
+
)
|
177
|
+
|
178
|
+
args = args | (request.view_args or {})
|
179
|
+
if param not in args:
|
180
|
+
# Special handling for run_id
|
181
|
+
if param == "run_id":
|
182
|
+
return _get_request_param("run_uuid")
|
183
|
+
raise MlflowException(
|
184
|
+
f"Missing value for required parameter '{param}'. "
|
185
|
+
"See the API docs for more information about request parameters.",
|
186
|
+
INVALID_PARAMETER_VALUE,
|
187
|
+
)
|
188
|
+
return args[param]
|
189
|
+
|
190
|
+
|
191
|
+
def _get_permission_from_store_or_default(store_permission_func: Callable[[], str]) -> Permission:
|
192
|
+
"""
|
193
|
+
Attempts to get permission from store,
|
194
|
+
and returns default permission if no record is found.
|
195
|
+
"""
|
196
|
+
try:
|
197
|
+
perm = store_permission_func()
|
198
|
+
except MlflowException as e:
|
199
|
+
if e.error_code == ErrorCode.Name(RESOURCE_DOES_NOT_EXIST):
|
200
|
+
perm = auth_config.default_permission
|
201
|
+
else:
|
202
|
+
raise
|
203
|
+
return get_permission(perm)
|
204
|
+
|
205
|
+
|
206
|
+
def _get_permission_from_experiment_id() -> Permission:
|
207
|
+
experiment_id = _get_request_param("experiment_id")
|
208
|
+
username = authenticate_request().username
|
209
|
+
return _get_permission_from_store_or_default(
|
210
|
+
lambda: store.get_experiment_permission(experiment_id, username).permission
|
211
|
+
)
|
212
|
+
|
213
|
+
|
214
|
+
_EXPERIMENT_ID_PATTERN = re.compile(r"^(\d+)/")
|
215
|
+
|
216
|
+
|
217
|
+
def _get_experiment_id_from_view_args():
|
218
|
+
if artifact_path := request.view_args.get("artifact_path"):
|
219
|
+
if m := _EXPERIMENT_ID_PATTERN.match(artifact_path):
|
220
|
+
return m.group(1)
|
221
|
+
return None
|
222
|
+
|
223
|
+
|
224
|
+
def _get_permission_from_experiment_id_artifact_proxy() -> Permission:
|
225
|
+
if experiment_id := _get_experiment_id_from_view_args():
|
226
|
+
username = authenticate_request().username
|
227
|
+
return _get_permission_from_store_or_default(
|
228
|
+
lambda: store.get_experiment_permission(experiment_id, username).permission
|
229
|
+
)
|
230
|
+
return get_permission(auth_config.default_permission)
|
231
|
+
|
232
|
+
|
233
|
+
def _get_permission_from_experiment_name() -> Permission:
|
234
|
+
experiment_name = _get_request_param("experiment_name")
|
235
|
+
store_exp = _get_tracking_store().get_experiment_by_name(experiment_name)
|
236
|
+
if store_exp is None:
|
237
|
+
raise MlflowException(
|
238
|
+
f"Could not find experiment with name {experiment_name}",
|
239
|
+
error_code=RESOURCE_DOES_NOT_EXIST,
|
240
|
+
)
|
241
|
+
username = authenticate_request().username
|
242
|
+
return _get_permission_from_store_or_default(
|
243
|
+
lambda: store.get_experiment_permission(store_exp.experiment_id, username).permission
|
244
|
+
)
|
245
|
+
|
246
|
+
|
247
|
+
def _get_permission_from_run_id() -> Permission:
|
248
|
+
# run permissions inherit from parent resource (experiment)
|
249
|
+
# so we just get the experiment permission
|
250
|
+
run_id = _get_request_param("run_id")
|
251
|
+
run = _get_tracking_store().get_run(run_id)
|
252
|
+
experiment_id = run.info.experiment_id
|
253
|
+
username = authenticate_request().username
|
254
|
+
return _get_permission_from_store_or_default(
|
255
|
+
lambda: store.get_experiment_permission(experiment_id, username).permission
|
256
|
+
)
|
257
|
+
|
258
|
+
|
259
|
+
def _get_permission_from_model_id() -> Permission:
|
260
|
+
# logged model permissions inherit from parent resource (experiment)
|
261
|
+
model_id = _get_request_param("model_id")
|
262
|
+
model = _get_tracking_store().get_logged_model(model_id)
|
263
|
+
experiment_id = model.experiment_id
|
264
|
+
username = authenticate_request().username
|
265
|
+
return _get_permission_from_store_or_default(
|
266
|
+
lambda: store.get_experiment_permission(experiment_id, username).permission
|
267
|
+
)
|
268
|
+
|
269
|
+
|
270
|
+
def _get_permission_from_registered_model_name() -> Permission:
|
271
|
+
name = _get_request_param("name")
|
272
|
+
username = authenticate_request().username
|
273
|
+
return _get_permission_from_store_or_default(
|
274
|
+
lambda: store.get_registered_model_permission(name, username).permission
|
275
|
+
)
|
276
|
+
|
277
|
+
|
278
|
+
def validate_can_read_experiment():
|
279
|
+
return _get_permission_from_experiment_id().can_read
|
280
|
+
|
281
|
+
|
282
|
+
def validate_can_read_experiment_by_name():
|
283
|
+
return _get_permission_from_experiment_name().can_read
|
284
|
+
|
285
|
+
|
286
|
+
def validate_can_update_experiment():
|
287
|
+
return _get_permission_from_experiment_id().can_update
|
288
|
+
|
289
|
+
|
290
|
+
def validate_can_delete_experiment():
|
291
|
+
return _get_permission_from_experiment_id().can_delete
|
292
|
+
|
293
|
+
|
294
|
+
def validate_can_manage_experiment():
|
295
|
+
return _get_permission_from_experiment_id().can_manage
|
296
|
+
|
297
|
+
|
298
|
+
def validate_can_read_experiment_artifact_proxy():
|
299
|
+
return _get_permission_from_experiment_id_artifact_proxy().can_read
|
300
|
+
|
301
|
+
|
302
|
+
def validate_can_update_experiment_artifact_proxy():
|
303
|
+
return _get_permission_from_experiment_id_artifact_proxy().can_update
|
304
|
+
|
305
|
+
|
306
|
+
def validate_can_delete_experiment_artifact_proxy():
|
307
|
+
return _get_permission_from_experiment_id_artifact_proxy().can_manage
|
308
|
+
|
309
|
+
|
310
|
+
# Runs
|
311
|
+
def validate_can_read_run():
|
312
|
+
return _get_permission_from_run_id().can_read
|
313
|
+
|
314
|
+
|
315
|
+
def validate_can_update_run():
|
316
|
+
return _get_permission_from_run_id().can_update
|
317
|
+
|
318
|
+
|
319
|
+
def validate_can_delete_run():
|
320
|
+
return _get_permission_from_run_id().can_delete
|
321
|
+
|
322
|
+
|
323
|
+
def validate_can_manage_run():
|
324
|
+
return _get_permission_from_run_id().can_manage
|
325
|
+
|
326
|
+
|
327
|
+
# Logged models
|
328
|
+
def validate_can_read_logged_model():
|
329
|
+
return _get_permission_from_model_id().can_read
|
330
|
+
|
331
|
+
|
332
|
+
def validate_can_update_logged_model():
|
333
|
+
return _get_permission_from_model_id().can_update
|
334
|
+
|
335
|
+
|
336
|
+
def validate_can_delete_logged_model():
|
337
|
+
return _get_permission_from_model_id().can_delete
|
338
|
+
|
339
|
+
|
340
|
+
def validate_can_manage_logged_model():
|
341
|
+
return _get_permission_from_model_id().can_manage
|
342
|
+
|
343
|
+
|
344
|
+
# Registered models
|
345
|
+
def validate_can_read_registered_model():
|
346
|
+
return _get_permission_from_registered_model_name().can_read
|
347
|
+
|
348
|
+
|
349
|
+
def validate_can_update_registered_model():
|
350
|
+
return _get_permission_from_registered_model_name().can_update
|
351
|
+
|
352
|
+
|
353
|
+
def validate_can_delete_registered_model():
|
354
|
+
return _get_permission_from_registered_model_name().can_delete
|
355
|
+
|
356
|
+
|
357
|
+
def validate_can_manage_registered_model():
|
358
|
+
return _get_permission_from_registered_model_name().can_manage
|
359
|
+
|
360
|
+
|
361
|
+
def sender_is_admin():
|
362
|
+
"""Validate if the sender is admin"""
|
363
|
+
username = authenticate_request().username
|
364
|
+
return store.get_user(username).is_admin
|
365
|
+
|
366
|
+
|
367
|
+
def username_is_sender():
|
368
|
+
"""Validate if the request username is the sender"""
|
369
|
+
username = _get_request_param("username")
|
370
|
+
sender = authenticate_request().username
|
371
|
+
return username == sender
|
372
|
+
|
373
|
+
|
374
|
+
def validate_can_read_user():
|
375
|
+
return username_is_sender()
|
376
|
+
|
377
|
+
|
378
|
+
def validate_can_create_user():
|
379
|
+
# only admins can create user, but admins won't reach this validator
|
380
|
+
return False
|
381
|
+
|
382
|
+
|
383
|
+
def validate_can_update_user_password():
|
384
|
+
return username_is_sender()
|
385
|
+
|
386
|
+
|
387
|
+
def validate_can_update_user_admin():
|
388
|
+
# only admins can update, but admins won't reach this validator
|
389
|
+
return False
|
390
|
+
|
391
|
+
|
392
|
+
def validate_can_delete_user():
|
393
|
+
# only admins can delete, but admins won't reach this validator
|
394
|
+
return False
|
395
|
+
|
396
|
+
|
397
|
+
BEFORE_REQUEST_HANDLERS = {
|
398
|
+
# Routes for experiments
|
399
|
+
GetExperiment: validate_can_read_experiment,
|
400
|
+
GetExperimentByName: validate_can_read_experiment_by_name,
|
401
|
+
DeleteExperiment: validate_can_delete_experiment,
|
402
|
+
RestoreExperiment: validate_can_delete_experiment,
|
403
|
+
UpdateExperiment: validate_can_update_experiment,
|
404
|
+
SetExperimentTag: validate_can_update_experiment,
|
405
|
+
# Routes for runs
|
406
|
+
CreateRun: validate_can_update_experiment,
|
407
|
+
GetRun: validate_can_read_run,
|
408
|
+
DeleteRun: validate_can_delete_run,
|
409
|
+
RestoreRun: validate_can_delete_run,
|
410
|
+
UpdateRun: validate_can_update_run,
|
411
|
+
LogMetric: validate_can_update_run,
|
412
|
+
LogBatch: validate_can_update_run,
|
413
|
+
LogModel: validate_can_update_run,
|
414
|
+
SetTag: validate_can_update_run,
|
415
|
+
DeleteTag: validate_can_update_run,
|
416
|
+
LogParam: validate_can_update_run,
|
417
|
+
GetMetricHistory: validate_can_read_run,
|
418
|
+
ListArtifacts: validate_can_read_run,
|
419
|
+
# Routes for model registry
|
420
|
+
GetRegisteredModel: validate_can_read_registered_model,
|
421
|
+
DeleteRegisteredModel: validate_can_delete_registered_model,
|
422
|
+
UpdateRegisteredModel: validate_can_update_registered_model,
|
423
|
+
RenameRegisteredModel: validate_can_update_registered_model,
|
424
|
+
GetLatestVersions: validate_can_read_registered_model,
|
425
|
+
CreateModelVersion: validate_can_update_registered_model,
|
426
|
+
GetModelVersion: validate_can_read_registered_model,
|
427
|
+
DeleteModelVersion: validate_can_delete_registered_model,
|
428
|
+
UpdateModelVersion: validate_can_update_registered_model,
|
429
|
+
TransitionModelVersionStage: validate_can_update_registered_model,
|
430
|
+
GetModelVersionDownloadUri: validate_can_read_registered_model,
|
431
|
+
SetRegisteredModelTag: validate_can_update_registered_model,
|
432
|
+
DeleteRegisteredModelTag: validate_can_update_registered_model,
|
433
|
+
SetModelVersionTag: validate_can_update_registered_model,
|
434
|
+
DeleteModelVersionTag: validate_can_delete_registered_model,
|
435
|
+
SetRegisteredModelAlias: validate_can_update_registered_model,
|
436
|
+
DeleteRegisteredModelAlias: validate_can_delete_registered_model,
|
437
|
+
GetModelVersionByAlias: validate_can_read_registered_model,
|
438
|
+
}
|
439
|
+
|
440
|
+
|
441
|
+
def get_before_request_handler(request_class):
|
442
|
+
return BEFORE_REQUEST_HANDLERS.get(request_class)
|
443
|
+
|
444
|
+
|
445
|
+
def _re_compile_path(path: str) -> re.Pattern:
|
446
|
+
"""
|
447
|
+
Convert a path with angle brackets to a regex pattern. For example,
|
448
|
+
"/api/2.0/experiments/<experiment_id>" becomes "/api/2.0/experiments/([^/]+)".
|
449
|
+
"""
|
450
|
+
return re.compile(re.sub(r"<([^>]+)>", r"([^/]+)", path))
|
451
|
+
|
452
|
+
|
453
|
+
BEFORE_REQUEST_VALIDATORS = {
|
454
|
+
(http_path, method): handler
|
455
|
+
for http_path, handler, methods in get_endpoints(get_before_request_handler)
|
456
|
+
for method in methods
|
457
|
+
}
|
458
|
+
|
459
|
+
BEFORE_REQUEST_VALIDATORS.update(
|
460
|
+
{
|
461
|
+
(SIGNUP, "GET"): validate_can_create_user,
|
462
|
+
(GET_USER, "GET"): validate_can_read_user,
|
463
|
+
(CREATE_USER, "POST"): validate_can_create_user,
|
464
|
+
(UPDATE_USER_PASSWORD, "PATCH"): validate_can_update_user_password,
|
465
|
+
(UPDATE_USER_ADMIN, "PATCH"): validate_can_update_user_admin,
|
466
|
+
(DELETE_USER, "DELETE"): validate_can_delete_user,
|
467
|
+
(GET_EXPERIMENT_PERMISSION, "GET"): validate_can_manage_experiment,
|
468
|
+
(CREATE_EXPERIMENT_PERMISSION, "POST"): validate_can_manage_experiment,
|
469
|
+
(UPDATE_EXPERIMENT_PERMISSION, "PATCH"): validate_can_manage_experiment,
|
470
|
+
(DELETE_EXPERIMENT_PERMISSION, "DELETE"): validate_can_manage_experiment,
|
471
|
+
(GET_REGISTERED_MODEL_PERMISSION, "GET"): validate_can_manage_registered_model,
|
472
|
+
(CREATE_REGISTERED_MODEL_PERMISSION, "POST"): validate_can_manage_registered_model,
|
473
|
+
(UPDATE_REGISTERED_MODEL_PERMISSION, "PATCH"): validate_can_manage_registered_model,
|
474
|
+
(DELETE_REGISTERED_MODEL_PERMISSION, "DELETE"): validate_can_manage_registered_model,
|
475
|
+
}
|
476
|
+
)
|
477
|
+
|
478
|
+
|
479
|
+
LOGGED_MODEL_BEFORE_REQUEST_HANDLERS = {
|
480
|
+
CreateLoggedModel: validate_can_update_experiment,
|
481
|
+
GetLoggedModel: validate_can_read_logged_model,
|
482
|
+
DeleteLoggedModel: validate_can_delete_logged_model,
|
483
|
+
FinalizeLoggedModel: validate_can_update_logged_model,
|
484
|
+
DeleteLoggedModelTag: validate_can_delete_logged_model,
|
485
|
+
SetLoggedModelTags: validate_can_update_logged_model,
|
486
|
+
LogLoggedModelParamsRequest: validate_can_update_logged_model,
|
487
|
+
}
|
488
|
+
|
489
|
+
|
490
|
+
def get_logged_model_before_request_handler(request_class):
|
491
|
+
return LOGGED_MODEL_BEFORE_REQUEST_HANDLERS.get(request_class)
|
492
|
+
|
493
|
+
|
494
|
+
LOGGED_MODEL_BEFORE_REQUEST_VALIDATORS = {
|
495
|
+
# Paths for logged models contains path parameters (e.g. /mlflow/logged-models/<model_id>)
|
496
|
+
(_re_compile_path(http_path), method): handler
|
497
|
+
for http_path, handler, methods in get_endpoints(get_logged_model_before_request_handler)
|
498
|
+
for method in methods
|
499
|
+
}
|
500
|
+
|
501
|
+
|
502
|
+
def _is_proxy_artifact_path(path: str) -> bool:
|
503
|
+
return path.startswith(f"{_REST_API_PATH_PREFIX}/mlflow-artifacts/artifacts/")
|
504
|
+
|
505
|
+
|
506
|
+
def _get_proxy_artifact_validator(
|
507
|
+
method: str, view_args: Optional[dict[str, Any]]
|
508
|
+
) -> Optional[Callable[[], bool]]:
|
509
|
+
if view_args is None:
|
510
|
+
return validate_can_read_experiment_artifact_proxy # List
|
511
|
+
|
512
|
+
return {
|
513
|
+
"GET": validate_can_read_experiment_artifact_proxy, # Download
|
514
|
+
"PUT": validate_can_update_experiment_artifact_proxy, # Upload
|
515
|
+
"DELETE": validate_can_delete_experiment_artifact_proxy, # Delete
|
516
|
+
}.get(method)
|
517
|
+
|
518
|
+
|
519
|
+
def authenticate_request() -> Union[Authorization, Response]:
|
520
|
+
"""Use configured authorization function to get request authorization."""
|
521
|
+
auth_func = get_auth_func(auth_config.authorization_function)
|
522
|
+
return auth_func()
|
523
|
+
|
524
|
+
|
525
|
+
@functools.lru_cache(maxsize=None)
|
526
|
+
def get_auth_func(authorization_function: str) -> Callable[[], Union[Authorization, Response]]:
|
527
|
+
"""
|
528
|
+
Import and return the specified authorization function.
|
529
|
+
|
530
|
+
Args:
|
531
|
+
authorization_function: A string of the form "module.submodule:auth_func"
|
532
|
+
"""
|
533
|
+
mod_name, fn_name = authorization_function.split(":", 1)
|
534
|
+
module = importlib.import_module(mod_name)
|
535
|
+
return getattr(module, fn_name)
|
536
|
+
|
537
|
+
|
538
|
+
def authenticate_request_basic_auth() -> Union[Authorization, Response]:
|
539
|
+
"""Authenticate the request using basic auth."""
|
540
|
+
if request.authorization is None:
|
541
|
+
return make_basic_auth_response()
|
542
|
+
|
543
|
+
username = request.authorization.username
|
544
|
+
password = request.authorization.password
|
545
|
+
if store.authenticate_user(username, password):
|
546
|
+
return request.authorization
|
547
|
+
else:
|
548
|
+
# let user attempt login again
|
549
|
+
return make_basic_auth_response()
|
550
|
+
|
551
|
+
|
552
|
+
def _find_validator(req: Request) -> Optional[Callable[[], bool]]:
|
553
|
+
"""
|
554
|
+
Finds the validator matching the request path and method.
|
555
|
+
"""
|
556
|
+
if "/mlflow/logged-models" in req.path:
|
557
|
+
# logged model routes are not registered in the app
|
558
|
+
# so we need to check them manually
|
559
|
+
return next(
|
560
|
+
(
|
561
|
+
v
|
562
|
+
for (pat, method), v in LOGGED_MODEL_BEFORE_REQUEST_VALIDATORS.items()
|
563
|
+
if pat.fullmatch(req.path) and method == req.method
|
564
|
+
),
|
565
|
+
None,
|
566
|
+
)
|
567
|
+
else:
|
568
|
+
return BEFORE_REQUEST_VALIDATORS.get((req.path, req.method))
|
569
|
+
|
570
|
+
|
571
|
+
@catch_mlflow_exception
|
572
|
+
def _before_request():
|
573
|
+
if is_unprotected_route(request.path):
|
574
|
+
return
|
575
|
+
|
576
|
+
authorization = authenticate_request()
|
577
|
+
if isinstance(authorization, Response):
|
578
|
+
return authorization
|
579
|
+
elif not isinstance(authorization, Authorization):
|
580
|
+
raise MlflowException(
|
581
|
+
f"Unsupported result type from {auth_config.authorization_function}: "
|
582
|
+
f"'{type(authorization).__name__}'",
|
583
|
+
INTERNAL_ERROR,
|
584
|
+
)
|
585
|
+
|
586
|
+
# admins don't need to be authorized
|
587
|
+
if sender_is_admin():
|
588
|
+
return
|
589
|
+
|
590
|
+
# authorization
|
591
|
+
if validator := _find_validator(request):
|
592
|
+
if not validator():
|
593
|
+
return make_forbidden_response()
|
594
|
+
elif _is_proxy_artifact_path(request.path):
|
595
|
+
if validator := _get_proxy_artifact_validator(request.method, request.view_args):
|
596
|
+
if not validator():
|
597
|
+
return make_forbidden_response()
|
598
|
+
|
599
|
+
|
600
|
+
def set_can_manage_experiment_permission(resp: Response):
|
601
|
+
response_message = CreateExperiment.Response()
|
602
|
+
parse_dict(resp.json, response_message)
|
603
|
+
experiment_id = response_message.experiment_id
|
604
|
+
username = authenticate_request().username
|
605
|
+
store.create_experiment_permission(experiment_id, username, MANAGE.name)
|
606
|
+
|
607
|
+
|
608
|
+
def set_can_manage_registered_model_permission(resp: Response):
|
609
|
+
response_message = CreateRegisteredModel.Response()
|
610
|
+
parse_dict(resp.json, response_message)
|
611
|
+
name = response_message.registered_model.name
|
612
|
+
username = authenticate_request().username
|
613
|
+
store.create_registered_model_permission(name, username, MANAGE.name)
|
614
|
+
|
615
|
+
|
616
|
+
def delete_can_manage_registered_model_permission(resp: Response):
|
617
|
+
"""
|
618
|
+
Delete registered model permission when the model is deleted.
|
619
|
+
|
620
|
+
We need to do this because the primary key of the registered model is the name,
|
621
|
+
unlike the experiment where the primary key is experiment_id (UUID). Therefore,
|
622
|
+
we have to delete the permission record when the model is deleted otherwise it
|
623
|
+
conflicts with the new model registered with the same name.
|
624
|
+
"""
|
625
|
+
# Get model name from request context because it's not available in the response
|
626
|
+
name = request.get_json(force=True, silent=True)["name"]
|
627
|
+
username = authenticate_request().username
|
628
|
+
store.delete_registered_model_permission(name, username)
|
629
|
+
|
630
|
+
|
631
|
+
def filter_search_experiments(resp: Response):
|
632
|
+
if sender_is_admin():
|
633
|
+
return
|
634
|
+
|
635
|
+
response_message = SearchExperiments.Response()
|
636
|
+
parse_dict(resp.json, response_message)
|
637
|
+
|
638
|
+
# fetch permissions
|
639
|
+
username = authenticate_request().username
|
640
|
+
perms = store.list_experiment_permissions(username)
|
641
|
+
can_read = {p.experiment_id: get_permission(p.permission).can_read for p in perms}
|
642
|
+
default_can_read = get_permission(auth_config.default_permission).can_read
|
643
|
+
|
644
|
+
# filter out unreadable
|
645
|
+
for e in list(response_message.experiments):
|
646
|
+
if not can_read.get(e.experiment_id, default_can_read):
|
647
|
+
response_message.experiments.remove(e)
|
648
|
+
|
649
|
+
# re-fetch to fill max results
|
650
|
+
request_message = _get_request_message(SearchExperiments())
|
651
|
+
while (
|
652
|
+
len(response_message.experiments) < request_message.max_results
|
653
|
+
and response_message.next_page_token != ""
|
654
|
+
):
|
655
|
+
refetched: PagedList[Experiment] = _get_tracking_store().search_experiments(
|
656
|
+
view_type=request_message.view_type,
|
657
|
+
max_results=request_message.max_results,
|
658
|
+
order_by=request_message.order_by,
|
659
|
+
filter_string=request_message.filter,
|
660
|
+
page_token=response_message.next_page_token,
|
661
|
+
)
|
662
|
+
refetched = refetched[: request_message.max_results - len(response_message.experiments)]
|
663
|
+
if len(refetched) == 0:
|
664
|
+
response_message.next_page_token = ""
|
665
|
+
break
|
666
|
+
|
667
|
+
refetched_readable_proto = [
|
668
|
+
e.to_proto() for e in refetched if can_read.get(e.experiment_id, default_can_read)
|
669
|
+
]
|
670
|
+
response_message.experiments.extend(refetched_readable_proto)
|
671
|
+
|
672
|
+
# recalculate next page token
|
673
|
+
start_offset = SearchUtils.parse_start_offset_from_page_token(
|
674
|
+
response_message.next_page_token
|
675
|
+
)
|
676
|
+
final_offset = start_offset + len(refetched)
|
677
|
+
response_message.next_page_token = SearchUtils.create_page_token(final_offset)
|
678
|
+
|
679
|
+
resp.data = message_to_json(response_message)
|
680
|
+
|
681
|
+
|
682
|
+
def filter_search_logged_models(resp: Response) -> None:
|
683
|
+
"""
|
684
|
+
Filter out unreadable logged models from the search results.
|
685
|
+
"""
|
686
|
+
from mlflow.utils.search_utils import SearchLoggedModelsPaginationToken as Token
|
687
|
+
|
688
|
+
if sender_is_admin():
|
689
|
+
return
|
690
|
+
|
691
|
+
response_proto = SearchLoggedModels.Response()
|
692
|
+
parse_dict(resp.json, response_proto)
|
693
|
+
|
694
|
+
# fetch permissions
|
695
|
+
username = authenticate_request().username
|
696
|
+
perms = store.list_experiment_permissions(username)
|
697
|
+
can_read = {p.experiment_id: get_permission(p.permission).can_read for p in perms}
|
698
|
+
default_can_read = get_permission(auth_config.default_permission).can_read
|
699
|
+
|
700
|
+
# Remove unreadable models
|
701
|
+
for m in list(response_proto.models):
|
702
|
+
if not can_read.get(m.info.experiment_id, default_can_read):
|
703
|
+
response_proto.models.remove(m)
|
704
|
+
|
705
|
+
request_proto = _get_request_message(SearchLoggedModels())
|
706
|
+
max_results = request_proto.max_results
|
707
|
+
# These parameters won't change in the loop
|
708
|
+
params = {
|
709
|
+
"experiment_ids": list(request_proto.experiment_ids),
|
710
|
+
"filter_string": request_proto.filter or None,
|
711
|
+
"order_by": (
|
712
|
+
[
|
713
|
+
{
|
714
|
+
"field_name": ob.field_name,
|
715
|
+
"ascending": ob.ascending,
|
716
|
+
"dataset_name": ob.dataset_name,
|
717
|
+
"dataset_digest": ob.dataset_digest,
|
718
|
+
}
|
719
|
+
for ob in request_proto.order_by
|
720
|
+
]
|
721
|
+
if request_proto.order_by
|
722
|
+
else None
|
723
|
+
),
|
724
|
+
}
|
725
|
+
next_page_token = response_proto.next_page_token or None
|
726
|
+
tracking_store = _get_tracking_store()
|
727
|
+
while len(response_proto.models) < max_results and next_page_token is not None:
|
728
|
+
batch: PagedList[LoggedModel] = tracking_store.search_logged_models(
|
729
|
+
max_results=max_results, page_token=next_page_token, **params
|
730
|
+
)
|
731
|
+
is_last_page = batch.token is None
|
732
|
+
offset = Token.decode(next_page_token).offset if next_page_token else 0
|
733
|
+
last_index = len(batch) - 1
|
734
|
+
for index, model in enumerate(batch):
|
735
|
+
if not can_read.get(model.experiment_id, default_can_read):
|
736
|
+
continue
|
737
|
+
response_proto.models.append(model.to_proto())
|
738
|
+
if len(response_proto.models) >= max_results:
|
739
|
+
next_page_token = (
|
740
|
+
None
|
741
|
+
if is_last_page and index == last_index
|
742
|
+
else Token(offset=offset + index + 1, **params).encode()
|
743
|
+
)
|
744
|
+
break
|
745
|
+
else:
|
746
|
+
# If we reach here, it means we have not reached the max results.
|
747
|
+
next_page_token = (
|
748
|
+
None if is_last_page else Token(offset=offset + max_results, **params).encode()
|
749
|
+
)
|
750
|
+
|
751
|
+
if next_page_token:
|
752
|
+
response_proto.next_page_token = next_page_token
|
753
|
+
resp.data = message_to_json(response_proto)
|
754
|
+
|
755
|
+
|
756
|
+
def filter_search_registered_models(resp: Response):
|
757
|
+
if sender_is_admin():
|
758
|
+
return
|
759
|
+
|
760
|
+
response_message = SearchRegisteredModels.Response()
|
761
|
+
parse_dict(resp.json, response_message)
|
762
|
+
|
763
|
+
# fetch permissions
|
764
|
+
username = authenticate_request().username
|
765
|
+
perms = store.list_registered_model_permissions(username)
|
766
|
+
can_read = {p.name: get_permission(p.permission).can_read for p in perms}
|
767
|
+
default_can_read = get_permission(auth_config.default_permission).can_read
|
768
|
+
|
769
|
+
# filter out unreadable
|
770
|
+
for rm in list(response_message.registered_models):
|
771
|
+
if not can_read.get(rm.name, default_can_read):
|
772
|
+
response_message.registered_models.remove(rm)
|
773
|
+
|
774
|
+
# re-fetch to fill max results
|
775
|
+
request_message = _get_request_message(SearchRegisteredModels())
|
776
|
+
while (
|
777
|
+
len(response_message.registered_models) < request_message.max_results
|
778
|
+
and response_message.next_page_token != ""
|
779
|
+
):
|
780
|
+
refetched: PagedList[RegisteredModel] = (
|
781
|
+
_get_model_registry_store().search_registered_models(
|
782
|
+
filter_string=request_message.filter,
|
783
|
+
max_results=request_message.max_results,
|
784
|
+
order_by=request_message.order_by,
|
785
|
+
page_token=response_message.next_page_token,
|
786
|
+
)
|
787
|
+
)
|
788
|
+
refetched = refetched[
|
789
|
+
: request_message.max_results - len(response_message.registered_models)
|
790
|
+
]
|
791
|
+
if len(refetched) == 0:
|
792
|
+
response_message.next_page_token = ""
|
793
|
+
break
|
794
|
+
|
795
|
+
refetched_readable_proto = [
|
796
|
+
rm.to_proto() for rm in refetched if can_read.get(rm.name, default_can_read)
|
797
|
+
]
|
798
|
+
response_message.registered_models.extend(refetched_readable_proto)
|
799
|
+
|
800
|
+
# recalculate next page token
|
801
|
+
start_offset = SearchUtils.parse_start_offset_from_page_token(
|
802
|
+
response_message.next_page_token
|
803
|
+
)
|
804
|
+
final_offset = start_offset + len(refetched)
|
805
|
+
response_message.next_page_token = SearchUtils.create_page_token(final_offset)
|
806
|
+
|
807
|
+
resp.data = message_to_json(response_message)
|
808
|
+
|
809
|
+
|
810
|
+
def rename_registered_model_permission(resp: Response):
|
811
|
+
"""
|
812
|
+
A model registry can be assigned to multiple users with different permissions.
|
813
|
+
|
814
|
+
Changing the model registry name must be propagated to all users.
|
815
|
+
"""
|
816
|
+
# get registry model name before update
|
817
|
+
data = request.get_json(force=True, silent=True)
|
818
|
+
store.rename_registered_model_permissions(data.get("name"), data.get("new_name"))
|
819
|
+
|
820
|
+
|
821
|
+
AFTER_REQUEST_PATH_HANDLERS = {
|
822
|
+
CreateExperiment: set_can_manage_experiment_permission,
|
823
|
+
CreateRegisteredModel: set_can_manage_registered_model_permission,
|
824
|
+
DeleteRegisteredModel: delete_can_manage_registered_model_permission,
|
825
|
+
SearchExperiments: filter_search_experiments,
|
826
|
+
SearchLoggedModels: filter_search_logged_models,
|
827
|
+
SearchRegisteredModels: filter_search_registered_models,
|
828
|
+
RenameRegisteredModel: rename_registered_model_permission,
|
829
|
+
}
|
830
|
+
|
831
|
+
|
832
|
+
def get_after_request_handler(request_class):
|
833
|
+
return AFTER_REQUEST_PATH_HANDLERS.get(request_class)
|
834
|
+
|
835
|
+
|
836
|
+
AFTER_REQUEST_HANDLERS = {
|
837
|
+
(http_path, method): handler
|
838
|
+
for http_path, handler, methods in get_endpoints(get_after_request_handler)
|
839
|
+
for method in methods
|
840
|
+
if handler is not None and "/graphql" not in http_path
|
841
|
+
}
|
842
|
+
|
843
|
+
|
844
|
+
@catch_mlflow_exception
|
845
|
+
def _after_request(resp: Response):
|
846
|
+
if 400 <= resp.status_code < 600:
|
847
|
+
return resp
|
848
|
+
|
849
|
+
if handler := AFTER_REQUEST_HANDLERS.get((request.path, request.method)):
|
850
|
+
handler(resp)
|
851
|
+
return resp
|
852
|
+
|
853
|
+
|
854
|
+
def create_admin_user(username, password):
|
855
|
+
if not store.has_user(username):
|
856
|
+
try:
|
857
|
+
store.create_user(username, password, is_admin=True)
|
858
|
+
_logger.info(
|
859
|
+
f"Created admin user '{username}'. "
|
860
|
+
"It is recommended that you set a new password as soon as possible "
|
861
|
+
f"on {UPDATE_USER_PASSWORD}."
|
862
|
+
)
|
863
|
+
except MlflowException as e:
|
864
|
+
if isinstance(e.__cause__, sqlalchemy.exc.IntegrityError):
|
865
|
+
# When multiple workers are starting up at the same time, it's possible
|
866
|
+
# that they try to create the admin user at the same time and one of them
|
867
|
+
# will succeed while the others will fail with an IntegrityError.
|
868
|
+
return
|
869
|
+
raise
|
870
|
+
|
871
|
+
|
872
|
+
def alert(href: str):
|
873
|
+
return render_template_string(
|
874
|
+
r"""
|
875
|
+
<script type = "text/javascript">
|
876
|
+
{% with messages = get_flashed_messages() %}
|
877
|
+
{% if messages %}
|
878
|
+
{% for message in messages %}
|
879
|
+
alert("{{ message }}");
|
880
|
+
{% endfor %}
|
881
|
+
{% endif %}
|
882
|
+
{% endwith %}
|
883
|
+
window.location.href = "{{ href }}";
|
884
|
+
</script>
|
885
|
+
""",
|
886
|
+
href=href,
|
887
|
+
)
|
888
|
+
|
889
|
+
|
890
|
+
def signup():
|
891
|
+
return render_template_string(
|
892
|
+
r"""
|
893
|
+
<style>
|
894
|
+
form {
|
895
|
+
background-color: #F5F5F5;
|
896
|
+
border: 1px solid #CCCCCC;
|
897
|
+
border-radius: 4px;
|
898
|
+
padding: 20px;
|
899
|
+
max-width: 400px;
|
900
|
+
margin: 0 auto;
|
901
|
+
font-family: Arial, sans-serif;
|
902
|
+
font-size: 14px;
|
903
|
+
line-height: 1.5;
|
904
|
+
}
|
905
|
+
|
906
|
+
input[type=text], input[type=password] {
|
907
|
+
width: 100%;
|
908
|
+
padding: 10px;
|
909
|
+
margin-bottom: 10px;
|
910
|
+
border: 1px solid #CCCCCC;
|
911
|
+
border-radius: 4px;
|
912
|
+
box-sizing: border-box;
|
913
|
+
}
|
914
|
+
input[type=submit] {
|
915
|
+
background-color: rgb(34, 114, 180);
|
916
|
+
color: #FFFFFF;
|
917
|
+
border: none;
|
918
|
+
border-radius: 4px;
|
919
|
+
padding: 10px 20px;
|
920
|
+
cursor: pointer;
|
921
|
+
font-size: 16px;
|
922
|
+
font-weight: bold;
|
923
|
+
}
|
924
|
+
|
925
|
+
input[type=submit]:hover {
|
926
|
+
background-color: rgb(14, 83, 139);
|
927
|
+
}
|
928
|
+
|
929
|
+
.logo-container {
|
930
|
+
display: flex;
|
931
|
+
align-items: center;
|
932
|
+
justify-content: center;
|
933
|
+
margin-bottom: 10px;
|
934
|
+
}
|
935
|
+
|
936
|
+
.logo {
|
937
|
+
max-width: 150px;
|
938
|
+
margin-right: 10px;
|
939
|
+
}
|
940
|
+
</style>
|
941
|
+
|
942
|
+
<form action="{{ users_route }}" method="post">
|
943
|
+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
944
|
+
<div class="logo-container">
|
945
|
+
{% autoescape false %}
|
946
|
+
{{ mlflow_logo }}
|
947
|
+
{% endautoescape %}
|
948
|
+
</div>
|
949
|
+
<label for="username">Username:</label>
|
950
|
+
<br>
|
951
|
+
<input type="text" id="username" name="username" minlength="4">
|
952
|
+
<br>
|
953
|
+
<label for="password">Password:</label>
|
954
|
+
<br>
|
955
|
+
<input type="password" id="password" name="password" minlength="12">
|
956
|
+
<br>
|
957
|
+
<br>
|
958
|
+
<input type="submit" value="Sign up">
|
959
|
+
</form>
|
960
|
+
""",
|
961
|
+
mlflow_logo=MLFLOW_LOGO,
|
962
|
+
users_route=CREATE_USER_UI,
|
963
|
+
)
|
964
|
+
|
965
|
+
|
966
|
+
@catch_mlflow_exception
|
967
|
+
def create_user_ui(csrf):
|
968
|
+
csrf.protect()
|
969
|
+
content_type = request.headers.get("Content-Type")
|
970
|
+
if content_type == "application/x-www-form-urlencoded":
|
971
|
+
username = request.form["username"]
|
972
|
+
password = request.form["password"]
|
973
|
+
|
974
|
+
if not username or not password:
|
975
|
+
message = "Username and password cannot be empty."
|
976
|
+
return make_response(message, 400)
|
977
|
+
|
978
|
+
if store.has_user(username):
|
979
|
+
flash(f"Username has already been taken: {username}")
|
980
|
+
return alert(href=SIGNUP)
|
981
|
+
|
982
|
+
store.create_user(username, password)
|
983
|
+
flash(f"Successfully signed up user: {username}")
|
984
|
+
return alert(href=HOME)
|
985
|
+
else:
|
986
|
+
message = "Invalid content type. Must be application/x-www-form-urlencoded"
|
987
|
+
return make_response(message, 400)
|
988
|
+
|
989
|
+
|
990
|
+
@catch_mlflow_exception
|
991
|
+
def create_user():
|
992
|
+
content_type = request.headers.get("Content-Type")
|
993
|
+
if content_type == "application/json":
|
994
|
+
username = _get_request_param("username")
|
995
|
+
password = _get_request_param("password")
|
996
|
+
|
997
|
+
if not username or not password:
|
998
|
+
message = "Username and password cannot be empty."
|
999
|
+
return make_response(message, 400)
|
1000
|
+
|
1001
|
+
user = store.create_user(username, password)
|
1002
|
+
return jsonify({"user": user.to_json()})
|
1003
|
+
else:
|
1004
|
+
message = "Invalid content type. Must be application/json"
|
1005
|
+
return make_response(message, 400)
|
1006
|
+
|
1007
|
+
|
1008
|
+
@catch_mlflow_exception
|
1009
|
+
def get_user():
|
1010
|
+
username = _get_request_param("username")
|
1011
|
+
user = store.get_user(username)
|
1012
|
+
return jsonify({"user": user.to_json()})
|
1013
|
+
|
1014
|
+
|
1015
|
+
@catch_mlflow_exception
|
1016
|
+
def update_user_password():
|
1017
|
+
username = _get_request_param("username")
|
1018
|
+
password = _get_request_param("password")
|
1019
|
+
store.update_user(username, password=password)
|
1020
|
+
return make_response({})
|
1021
|
+
|
1022
|
+
|
1023
|
+
@catch_mlflow_exception
|
1024
|
+
def update_user_admin():
|
1025
|
+
username = _get_request_param("username")
|
1026
|
+
is_admin = _get_request_param("is_admin")
|
1027
|
+
store.update_user(username, is_admin=is_admin)
|
1028
|
+
return make_response({})
|
1029
|
+
|
1030
|
+
|
1031
|
+
@catch_mlflow_exception
|
1032
|
+
def delete_user():
|
1033
|
+
username = _get_request_param("username")
|
1034
|
+
store.delete_user(username)
|
1035
|
+
return make_response({})
|
1036
|
+
|
1037
|
+
|
1038
|
+
@catch_mlflow_exception
|
1039
|
+
def create_experiment_permission():
|
1040
|
+
experiment_id = _get_request_param("experiment_id")
|
1041
|
+
username = _get_request_param("username")
|
1042
|
+
permission = _get_request_param("permission")
|
1043
|
+
ep = store.create_experiment_permission(experiment_id, username, permission)
|
1044
|
+
return jsonify({"experiment_permission": ep.to_json()})
|
1045
|
+
|
1046
|
+
|
1047
|
+
@catch_mlflow_exception
|
1048
|
+
def get_experiment_permission():
|
1049
|
+
experiment_id = _get_request_param("experiment_id")
|
1050
|
+
username = _get_request_param("username")
|
1051
|
+
ep = store.get_experiment_permission(experiment_id, username)
|
1052
|
+
return make_response({"experiment_permission": ep.to_json()})
|
1053
|
+
|
1054
|
+
|
1055
|
+
@catch_mlflow_exception
|
1056
|
+
def update_experiment_permission():
|
1057
|
+
experiment_id = _get_request_param("experiment_id")
|
1058
|
+
username = _get_request_param("username")
|
1059
|
+
permission = _get_request_param("permission")
|
1060
|
+
store.update_experiment_permission(experiment_id, username, permission)
|
1061
|
+
return make_response({})
|
1062
|
+
|
1063
|
+
|
1064
|
+
@catch_mlflow_exception
|
1065
|
+
def delete_experiment_permission():
|
1066
|
+
experiment_id = _get_request_param("experiment_id")
|
1067
|
+
username = _get_request_param("username")
|
1068
|
+
store.delete_experiment_permission(experiment_id, username)
|
1069
|
+
return make_response({})
|
1070
|
+
|
1071
|
+
|
1072
|
+
@catch_mlflow_exception
|
1073
|
+
def create_registered_model_permission():
|
1074
|
+
name = _get_request_param("name")
|
1075
|
+
username = _get_request_param("username")
|
1076
|
+
permission = _get_request_param("permission")
|
1077
|
+
rmp = store.create_registered_model_permission(name, username, permission)
|
1078
|
+
return make_response({"registered_model_permission": rmp.to_json()})
|
1079
|
+
|
1080
|
+
|
1081
|
+
@catch_mlflow_exception
|
1082
|
+
def get_registered_model_permission():
|
1083
|
+
name = _get_request_param("name")
|
1084
|
+
username = _get_request_param("username")
|
1085
|
+
rmp = store.get_registered_model_permission(name, username)
|
1086
|
+
return make_response({"registered_model_permission": rmp.to_json()})
|
1087
|
+
|
1088
|
+
|
1089
|
+
@catch_mlflow_exception
|
1090
|
+
def update_registered_model_permission():
|
1091
|
+
name = _get_request_param("name")
|
1092
|
+
username = _get_request_param("username")
|
1093
|
+
permission = _get_request_param("permission")
|
1094
|
+
store.update_registered_model_permission(name, username, permission)
|
1095
|
+
return make_response({})
|
1096
|
+
|
1097
|
+
|
1098
|
+
@catch_mlflow_exception
|
1099
|
+
def delete_registered_model_permission():
|
1100
|
+
name = _get_request_param("name")
|
1101
|
+
username = _get_request_param("username")
|
1102
|
+
store.delete_registered_model_permission(name, username)
|
1103
|
+
return make_response({})
|
1104
|
+
|
1105
|
+
|
1106
|
+
def create_app(app: Flask = app):
|
1107
|
+
"""
|
1108
|
+
A factory to enable authentication and authorization for the MLflow server.
|
1109
|
+
|
1110
|
+
Args:
|
1111
|
+
app: The Flask app to enable authentication and authorization for.
|
1112
|
+
|
1113
|
+
Returns:
|
1114
|
+
The app with authentication and authorization enabled.
|
1115
|
+
"""
|
1116
|
+
_logger.warning(
|
1117
|
+
"This feature is still experimental and may change in a future release without warning"
|
1118
|
+
)
|
1119
|
+
|
1120
|
+
# a secret key is required for flashing, and also for
|
1121
|
+
# CSRF protection. it's important that this is a static key,
|
1122
|
+
# otherwise CSRF validation won't work across workers.
|
1123
|
+
secret_key = MLFLOW_FLASK_SERVER_SECRET_KEY.get()
|
1124
|
+
if not secret_key:
|
1125
|
+
raise MlflowException(
|
1126
|
+
"A static secret key needs to be set for CSRF protection. Please set the "
|
1127
|
+
"`MLFLOW_FLASK_SERVER_SECRET_KEY` environment variable before starting the "
|
1128
|
+
"server. For example:\n\n"
|
1129
|
+
"export MLFLOW_FLASK_SERVER_SECRET_KEY='my-secret-key'\n\n"
|
1130
|
+
"If you are using multiple servers, please ensure this key is consistent between "
|
1131
|
+
"them, in order to prevent validation issues."
|
1132
|
+
)
|
1133
|
+
app.secret_key = secret_key
|
1134
|
+
|
1135
|
+
# we only need to protect the CREATE_USER_UI route, since that's
|
1136
|
+
# the only browser-accessible route. the rest are client / REST
|
1137
|
+
# APIs that do not have access to the CSRF token for validation
|
1138
|
+
app.config["WTF_CSRF_CHECK_DEFAULT"] = False
|
1139
|
+
csrf = CSRFProtect()
|
1140
|
+
csrf.init_app(app)
|
1141
|
+
|
1142
|
+
store.init_db(auth_config.database_uri)
|
1143
|
+
create_admin_user(auth_config.admin_username, auth_config.admin_password)
|
1144
|
+
|
1145
|
+
app.add_url_rule(
|
1146
|
+
rule=SIGNUP,
|
1147
|
+
view_func=signup,
|
1148
|
+
methods=["GET"],
|
1149
|
+
)
|
1150
|
+
app.add_url_rule(
|
1151
|
+
rule=CREATE_USER_UI,
|
1152
|
+
view_func=lambda: create_user_ui(csrf),
|
1153
|
+
methods=["POST"],
|
1154
|
+
)
|
1155
|
+
app.add_url_rule(
|
1156
|
+
rule=CREATE_USER,
|
1157
|
+
view_func=create_user,
|
1158
|
+
methods=["POST"],
|
1159
|
+
)
|
1160
|
+
app.add_url_rule(
|
1161
|
+
rule=GET_USER,
|
1162
|
+
view_func=get_user,
|
1163
|
+
methods=["GET"],
|
1164
|
+
)
|
1165
|
+
app.add_url_rule(
|
1166
|
+
rule=UPDATE_USER_PASSWORD,
|
1167
|
+
view_func=update_user_password,
|
1168
|
+
methods=["PATCH"],
|
1169
|
+
)
|
1170
|
+
app.add_url_rule(
|
1171
|
+
rule=UPDATE_USER_ADMIN,
|
1172
|
+
view_func=update_user_admin,
|
1173
|
+
methods=["PATCH"],
|
1174
|
+
)
|
1175
|
+
app.add_url_rule(
|
1176
|
+
rule=DELETE_USER,
|
1177
|
+
view_func=delete_user,
|
1178
|
+
methods=["DELETE"],
|
1179
|
+
)
|
1180
|
+
app.add_url_rule(
|
1181
|
+
rule=CREATE_EXPERIMENT_PERMISSION,
|
1182
|
+
view_func=create_experiment_permission,
|
1183
|
+
methods=["POST"],
|
1184
|
+
)
|
1185
|
+
app.add_url_rule(
|
1186
|
+
rule=GET_EXPERIMENT_PERMISSION,
|
1187
|
+
view_func=get_experiment_permission,
|
1188
|
+
methods=["GET"],
|
1189
|
+
)
|
1190
|
+
app.add_url_rule(
|
1191
|
+
rule=UPDATE_EXPERIMENT_PERMISSION,
|
1192
|
+
view_func=update_experiment_permission,
|
1193
|
+
methods=["PATCH"],
|
1194
|
+
)
|
1195
|
+
app.add_url_rule(
|
1196
|
+
rule=DELETE_EXPERIMENT_PERMISSION,
|
1197
|
+
view_func=delete_experiment_permission,
|
1198
|
+
methods=["DELETE"],
|
1199
|
+
)
|
1200
|
+
app.add_url_rule(
|
1201
|
+
rule=CREATE_REGISTERED_MODEL_PERMISSION,
|
1202
|
+
view_func=create_registered_model_permission,
|
1203
|
+
methods=["POST"],
|
1204
|
+
)
|
1205
|
+
app.add_url_rule(
|
1206
|
+
rule=GET_REGISTERED_MODEL_PERMISSION,
|
1207
|
+
view_func=get_registered_model_permission,
|
1208
|
+
methods=["GET"],
|
1209
|
+
)
|
1210
|
+
app.add_url_rule(
|
1211
|
+
rule=UPDATE_REGISTERED_MODEL_PERMISSION,
|
1212
|
+
view_func=update_registered_model_permission,
|
1213
|
+
methods=["PATCH"],
|
1214
|
+
)
|
1215
|
+
app.add_url_rule(
|
1216
|
+
rule=DELETE_REGISTERED_MODEL_PERMISSION,
|
1217
|
+
view_func=delete_registered_model_permission,
|
1218
|
+
methods=["DELETE"],
|
1219
|
+
)
|
1220
|
+
|
1221
|
+
app.before_request(_before_request)
|
1222
|
+
app.after_request(_after_request)
|
1223
|
+
|
1224
|
+
return app
|