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,1009 @@
|
|
1
|
+
import hashlib
|
2
|
+
import importlib.metadata
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import pathlib
|
6
|
+
import re
|
7
|
+
import shutil
|
8
|
+
import subprocess
|
9
|
+
import sys
|
10
|
+
import tempfile
|
11
|
+
from copy import deepcopy
|
12
|
+
from typing import Optional
|
13
|
+
|
14
|
+
import yaml
|
15
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
16
|
+
from packaging.version import Version
|
17
|
+
|
18
|
+
from mlflow.environment_variables import (
|
19
|
+
_MLFLOW_ACTIVE_MODEL_ID,
|
20
|
+
_MLFLOW_TESTING,
|
21
|
+
MLFLOW_EXPERIMENT_ID,
|
22
|
+
MLFLOW_INPUT_EXAMPLE_INFERENCE_TIMEOUT,
|
23
|
+
MLFLOW_LOCK_MODEL_DEPENDENCIES,
|
24
|
+
MLFLOW_REQUIREMENTS_INFERENCE_RAISE_ERRORS,
|
25
|
+
)
|
26
|
+
from mlflow.exceptions import MlflowException
|
27
|
+
from mlflow.protos.databricks_pb2 import INVALID_PARAMETER_VALUE
|
28
|
+
from mlflow.tracking import get_tracking_uri
|
29
|
+
from mlflow.tracking.fluent import _get_experiment_id, get_active_model_id
|
30
|
+
from mlflow.utils import PYTHON_VERSION
|
31
|
+
from mlflow.utils.databricks_utils import (
|
32
|
+
_get_databricks_serverless_env_vars,
|
33
|
+
get_databricks_env_vars,
|
34
|
+
is_databricks_connect,
|
35
|
+
is_in_databricks_runtime,
|
36
|
+
)
|
37
|
+
from mlflow.utils.os import is_windows
|
38
|
+
from mlflow.utils.process import _exec_cmd
|
39
|
+
from mlflow.utils.requirements_utils import (
|
40
|
+
_infer_requirements,
|
41
|
+
_parse_requirements,
|
42
|
+
warn_dependency_requirement_mismatches,
|
43
|
+
)
|
44
|
+
from mlflow.utils.timeout import MlflowTimeoutError, run_with_timeout
|
45
|
+
from mlflow.version import VERSION
|
46
|
+
|
47
|
+
_logger = logging.getLogger(__name__)
|
48
|
+
|
49
|
+
_conda_header = """\
|
50
|
+
name: mlflow-env
|
51
|
+
channels:
|
52
|
+
- conda-forge
|
53
|
+
"""
|
54
|
+
|
55
|
+
_CONDA_ENV_FILE_NAME = "conda.yaml"
|
56
|
+
_REQUIREMENTS_FILE_NAME = "requirements.txt"
|
57
|
+
_CONSTRAINTS_FILE_NAME = "constraints.txt"
|
58
|
+
_PYTHON_ENV_FILE_NAME = "python_env.yaml"
|
59
|
+
|
60
|
+
|
61
|
+
# Note this regular expression does not cover all possible patterns
|
62
|
+
_CONDA_DEPENDENCY_REGEX = re.compile(
|
63
|
+
r"^(?P<package>python|pip|setuptools|wheel)"
|
64
|
+
r"(?P<operator><|>|<=|>=|=|==|!=)?"
|
65
|
+
r"(?P<version>[\d.]+)?$"
|
66
|
+
)
|
67
|
+
|
68
|
+
|
69
|
+
class _PythonEnv:
|
70
|
+
BUILD_PACKAGES = ("pip", "setuptools", "wheel")
|
71
|
+
|
72
|
+
def __init__(self, python=None, build_dependencies=None, dependencies=None):
|
73
|
+
"""
|
74
|
+
Represents environment information for MLflow Models and Projects.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
python: Python version for the environment. If unspecified, defaults to the current
|
78
|
+
Python version.
|
79
|
+
build_dependencies: List of build dependencies for the environment that must
|
80
|
+
be installed before installing ``dependencies``. If unspecified,
|
81
|
+
defaults to an empty list.
|
82
|
+
dependencies: List of dependencies for the environment. If unspecified, defaults to
|
83
|
+
an empty list.
|
84
|
+
"""
|
85
|
+
if python is not None and not isinstance(python, str):
|
86
|
+
raise TypeError(f"`python` must be a string but got {type(python)}")
|
87
|
+
if build_dependencies is not None and not isinstance(build_dependencies, list):
|
88
|
+
raise TypeError(
|
89
|
+
f"`build_dependencies` must be a list but got {type(build_dependencies)}"
|
90
|
+
)
|
91
|
+
if dependencies is not None and not isinstance(dependencies, list):
|
92
|
+
raise TypeError(f"`dependencies` must be a list but got {type(dependencies)}")
|
93
|
+
|
94
|
+
self.python = python or PYTHON_VERSION
|
95
|
+
self.build_dependencies = build_dependencies or []
|
96
|
+
self.dependencies = dependencies or []
|
97
|
+
|
98
|
+
def __str__(self):
|
99
|
+
return str(self.to_dict())
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def current(cls):
|
103
|
+
return cls(
|
104
|
+
python=PYTHON_VERSION,
|
105
|
+
build_dependencies=cls.get_current_build_dependencies(),
|
106
|
+
dependencies=[f"-r {_REQUIREMENTS_FILE_NAME}"],
|
107
|
+
)
|
108
|
+
|
109
|
+
@staticmethod
|
110
|
+
def get_current_build_dependencies():
|
111
|
+
build_dependencies = []
|
112
|
+
for package in _PythonEnv.BUILD_PACKAGES:
|
113
|
+
version = _get_package_version(package)
|
114
|
+
dep = (package + "==" + version) if version else package
|
115
|
+
build_dependencies.append(dep)
|
116
|
+
return build_dependencies
|
117
|
+
|
118
|
+
def to_dict(self):
|
119
|
+
return self.__dict__.copy()
|
120
|
+
|
121
|
+
@classmethod
|
122
|
+
def from_dict(cls, dct):
|
123
|
+
return cls(**dct)
|
124
|
+
|
125
|
+
def to_yaml(self, path):
|
126
|
+
with open(path, "w") as f:
|
127
|
+
# Exclude None and empty lists
|
128
|
+
data = {k: v for k, v in self.to_dict().items() if v}
|
129
|
+
yaml.safe_dump(data, f, sort_keys=False, default_flow_style=False)
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def from_yaml(cls, path):
|
133
|
+
with open(path) as f:
|
134
|
+
return cls.from_dict(yaml.safe_load(f))
|
135
|
+
|
136
|
+
@staticmethod
|
137
|
+
def get_dependencies_from_conda_yaml(path):
|
138
|
+
with open(path) as f:
|
139
|
+
conda_env = yaml.safe_load(f)
|
140
|
+
|
141
|
+
python = None
|
142
|
+
build_dependencies = None
|
143
|
+
unmatched_dependencies = []
|
144
|
+
dependencies = None
|
145
|
+
for dep in conda_env.get("dependencies", []):
|
146
|
+
if isinstance(dep, str):
|
147
|
+
match = _CONDA_DEPENDENCY_REGEX.match(dep)
|
148
|
+
if not match:
|
149
|
+
unmatched_dependencies.append(dep)
|
150
|
+
continue
|
151
|
+
package = match.group("package")
|
152
|
+
operator = match.group("operator")
|
153
|
+
version = match.group("version")
|
154
|
+
|
155
|
+
# Python
|
156
|
+
if not python and package == "python":
|
157
|
+
if operator is None:
|
158
|
+
raise MlflowException.invalid_parameter_value(
|
159
|
+
f"Invalid dependency for python: {dep}. "
|
160
|
+
"It must be pinned (e.g. python=3.8.13)."
|
161
|
+
)
|
162
|
+
|
163
|
+
if operator in ("<", ">", "!="):
|
164
|
+
raise MlflowException(
|
165
|
+
f"Invalid version comparator for python: '{operator}'. "
|
166
|
+
"Must be one of ['<=', '>=', '=', '=='].",
|
167
|
+
error_code=INVALID_PARAMETER_VALUE,
|
168
|
+
)
|
169
|
+
python = version
|
170
|
+
continue
|
171
|
+
|
172
|
+
# Build packages
|
173
|
+
if build_dependencies is None:
|
174
|
+
build_dependencies = []
|
175
|
+
# "=" is an invalid operator for pip
|
176
|
+
operator = "==" if operator == "=" else operator
|
177
|
+
build_dependencies.append(package + (operator or "") + (version or ""))
|
178
|
+
elif _is_pip_deps(dep):
|
179
|
+
dependencies = dep["pip"]
|
180
|
+
else:
|
181
|
+
raise MlflowException(
|
182
|
+
f"Invalid conda dependency: {dep}. Must be str or dict in the form of "
|
183
|
+
'{"pip": [...]}',
|
184
|
+
error_code=INVALID_PARAMETER_VALUE,
|
185
|
+
)
|
186
|
+
|
187
|
+
if python is None:
|
188
|
+
_logger.warning(
|
189
|
+
f"{path} does not include a python version specification. "
|
190
|
+
f"Using the current python version {PYTHON_VERSION}."
|
191
|
+
)
|
192
|
+
python = PYTHON_VERSION
|
193
|
+
|
194
|
+
if unmatched_dependencies:
|
195
|
+
_logger.warning(
|
196
|
+
"The following conda dependencies will not be installed in the resulting "
|
197
|
+
"environment: %s",
|
198
|
+
unmatched_dependencies,
|
199
|
+
)
|
200
|
+
|
201
|
+
return {
|
202
|
+
"python": python,
|
203
|
+
"build_dependencies": build_dependencies,
|
204
|
+
"dependencies": dependencies,
|
205
|
+
}
|
206
|
+
|
207
|
+
@classmethod
|
208
|
+
def from_conda_yaml(cls, path):
|
209
|
+
return cls.from_dict(cls.get_dependencies_from_conda_yaml(path))
|
210
|
+
|
211
|
+
|
212
|
+
def _mlflow_conda_env( # noqa: D417
|
213
|
+
path=None,
|
214
|
+
additional_conda_deps=None,
|
215
|
+
additional_pip_deps=None,
|
216
|
+
additional_conda_channels=None,
|
217
|
+
install_mlflow=True,
|
218
|
+
):
|
219
|
+
"""Creates a Conda environment with the specified package channels and dependencies. If there
|
220
|
+
are any pip dependencies, including from the install_mlflow parameter, then pip will be added to
|
221
|
+
the conda dependencies. This is done to ensure that the pip inside the conda environment is
|
222
|
+
used to install the pip dependencies.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
path: Local filesystem path where the conda env file is to be written. If unspecified,
|
226
|
+
the conda env will not be written to the filesystem; it will still be returned
|
227
|
+
in dictionary format.
|
228
|
+
additional_conda_deps: List of additional conda dependencies passed as strings.
|
229
|
+
additional_pip_deps: List of additional pip dependencies passed as strings.
|
230
|
+
additional_conda_channels: List of additional conda channels to search when resolving
|
231
|
+
packages.
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
None if path is specified. Otherwise, the a dictionary representation of the
|
235
|
+
Conda environment.
|
236
|
+
|
237
|
+
"""
|
238
|
+
additional_pip_deps = additional_pip_deps or []
|
239
|
+
mlflow_deps = (
|
240
|
+
[f"mlflow=={VERSION}"]
|
241
|
+
if install_mlflow and not _contains_mlflow_requirement(additional_pip_deps)
|
242
|
+
else []
|
243
|
+
)
|
244
|
+
pip_deps = mlflow_deps + additional_pip_deps
|
245
|
+
conda_deps = additional_conda_deps if additional_conda_deps else []
|
246
|
+
if pip_deps:
|
247
|
+
pip_version = _get_package_version("pip")
|
248
|
+
if pip_version is not None:
|
249
|
+
# When a new version of pip is released on PyPI, it takes a while until that version is
|
250
|
+
# uploaded to conda-forge. This time lag causes `conda create` to fail with
|
251
|
+
# a `ResolvePackageNotFound` error. As a workaround for this issue, use `<=` instead
|
252
|
+
# of `==` so conda installs `pip_version - 1` when `pip_version` is unavailable.
|
253
|
+
conda_deps.append(f"pip<={pip_version}")
|
254
|
+
else:
|
255
|
+
_logger.warning(
|
256
|
+
"Failed to resolve installed pip version. ``pip`` will be added to conda.yaml"
|
257
|
+
" environment spec without a version specifier."
|
258
|
+
)
|
259
|
+
conda_deps.append("pip")
|
260
|
+
|
261
|
+
env = yaml.safe_load(_conda_header)
|
262
|
+
env["dependencies"] = [f"python={PYTHON_VERSION}"]
|
263
|
+
env["dependencies"] += conda_deps
|
264
|
+
env["dependencies"].append({"pip": pip_deps})
|
265
|
+
if additional_conda_channels is not None:
|
266
|
+
env["channels"] += additional_conda_channels
|
267
|
+
|
268
|
+
if path is not None:
|
269
|
+
with open(path, "w") as out:
|
270
|
+
yaml.safe_dump(env, stream=out, default_flow_style=False)
|
271
|
+
return None
|
272
|
+
else:
|
273
|
+
return env
|
274
|
+
|
275
|
+
|
276
|
+
def _get_package_version(package_name: str) -> Optional[str]:
|
277
|
+
try:
|
278
|
+
return importlib.metadata.version(package_name)
|
279
|
+
except importlib.metadata.PackageNotFoundError:
|
280
|
+
return None
|
281
|
+
|
282
|
+
|
283
|
+
def _mlflow_additional_pip_env(pip_deps, path=None):
|
284
|
+
requirements = "\n".join(pip_deps)
|
285
|
+
if path is not None:
|
286
|
+
with open(path, "w") as out:
|
287
|
+
out.write(requirements)
|
288
|
+
return None
|
289
|
+
else:
|
290
|
+
return requirements
|
291
|
+
|
292
|
+
|
293
|
+
def _is_pip_deps(dep):
|
294
|
+
"""
|
295
|
+
Returns True if `dep` is a dict representing pip dependencies
|
296
|
+
"""
|
297
|
+
return isinstance(dep, dict) and "pip" in dep
|
298
|
+
|
299
|
+
|
300
|
+
def _get_pip_deps(conda_env):
|
301
|
+
"""
|
302
|
+
Returns:
|
303
|
+
The pip dependencies from the conda env.
|
304
|
+
"""
|
305
|
+
if conda_env is not None:
|
306
|
+
for dep in conda_env["dependencies"]:
|
307
|
+
if _is_pip_deps(dep):
|
308
|
+
return dep["pip"]
|
309
|
+
return []
|
310
|
+
|
311
|
+
|
312
|
+
def _overwrite_pip_deps(conda_env, new_pip_deps):
|
313
|
+
"""
|
314
|
+
Overwrites the pip dependencies section in the given conda env dictionary.
|
315
|
+
|
316
|
+
{
|
317
|
+
"name": "env",
|
318
|
+
"channels": [...],
|
319
|
+
"dependencies": [
|
320
|
+
...,
|
321
|
+
"pip",
|
322
|
+
{"pip": [...]}, <- Overwrite this
|
323
|
+
],
|
324
|
+
}
|
325
|
+
"""
|
326
|
+
deps = conda_env.get("dependencies", [])
|
327
|
+
new_deps = []
|
328
|
+
contains_pip_deps = False
|
329
|
+
for dep in deps:
|
330
|
+
if _is_pip_deps(dep):
|
331
|
+
contains_pip_deps = True
|
332
|
+
new_deps.append({"pip": new_pip_deps})
|
333
|
+
else:
|
334
|
+
new_deps.append(dep)
|
335
|
+
|
336
|
+
if not contains_pip_deps:
|
337
|
+
new_deps.append({"pip": new_pip_deps})
|
338
|
+
|
339
|
+
return {**conda_env, "dependencies": new_deps}
|
340
|
+
|
341
|
+
|
342
|
+
def _log_pip_requirements(conda_env, path, requirements_file=_REQUIREMENTS_FILE_NAME):
|
343
|
+
pip_deps = _get_pip_deps(conda_env)
|
344
|
+
_mlflow_additional_pip_env(pip_deps, path=os.path.join(path, requirements_file))
|
345
|
+
|
346
|
+
|
347
|
+
def _parse_pip_requirements(pip_requirements):
|
348
|
+
"""Parses an iterable of pip requirement strings or a pip requirements file.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
pip_requirements: Either an iterable of pip requirement strings
|
352
|
+
(e.g. ``["scikit-learn", "-r requirements.txt"]``) or the string path to a pip
|
353
|
+
requirements file on the local filesystem (e.g. ``"requirements.txt"``). If ``None``,
|
354
|
+
an empty list will be returned.
|
355
|
+
|
356
|
+
Returns:
|
357
|
+
A tuple of parsed requirements and constraints.
|
358
|
+
"""
|
359
|
+
if pip_requirements is None:
|
360
|
+
return [], []
|
361
|
+
|
362
|
+
def _is_string(x):
|
363
|
+
return isinstance(x, str)
|
364
|
+
|
365
|
+
def _is_iterable(x):
|
366
|
+
try:
|
367
|
+
iter(x)
|
368
|
+
return True
|
369
|
+
except Exception:
|
370
|
+
return False
|
371
|
+
|
372
|
+
if _is_string(pip_requirements):
|
373
|
+
with open(pip_requirements) as f:
|
374
|
+
return _parse_pip_requirements(f.read().splitlines())
|
375
|
+
elif _is_iterable(pip_requirements) and all(map(_is_string, pip_requirements)):
|
376
|
+
requirements = []
|
377
|
+
constraints = []
|
378
|
+
for req_or_con in _parse_requirements(pip_requirements, is_constraint=False):
|
379
|
+
if req_or_con.is_constraint:
|
380
|
+
constraints.append(req_or_con.req_str)
|
381
|
+
else:
|
382
|
+
requirements.append(req_or_con.req_str)
|
383
|
+
|
384
|
+
return requirements, constraints
|
385
|
+
else:
|
386
|
+
raise TypeError(
|
387
|
+
"`pip_requirements` must be either a string path to a pip requirements file on the "
|
388
|
+
"local filesystem or an iterable of pip requirement strings, but got `{}`".format(
|
389
|
+
type(pip_requirements)
|
390
|
+
)
|
391
|
+
)
|
392
|
+
|
393
|
+
|
394
|
+
_INFER_PIP_REQUIREMENTS_GENERAL_ERROR_MESSAGE = (
|
395
|
+
"Encountered an unexpected error while inferring pip requirements "
|
396
|
+
"(model URI: {model_uri}, flavor: {flavor}). Fall back to return {fallback}. "
|
397
|
+
"Set logging level to DEBUG to see the full traceback. "
|
398
|
+
)
|
399
|
+
|
400
|
+
|
401
|
+
def infer_pip_requirements(model_uri, flavor, fallback=None, timeout=None, extra_env_vars=None):
|
402
|
+
"""Infers the pip requirements of the specified model by creating a subprocess and loading
|
403
|
+
the model in it to determine which packages are imported.
|
404
|
+
|
405
|
+
Args:
|
406
|
+
model_uri: The URI of the model.
|
407
|
+
flavor: The flavor name of the model.
|
408
|
+
fallback: If provided, an unexpected error during the inference procedure is swallowed
|
409
|
+
and the value of ``fallback`` is returned. Otherwise, the error is raised.
|
410
|
+
timeout: If specified, the inference operation is bound by the timeout (in seconds).
|
411
|
+
extra_env_vars: A dictionary of extra environment variables to pass to the subprocess.
|
412
|
+
Default to None.
|
413
|
+
|
414
|
+
Returns:
|
415
|
+
A list of inferred pip requirements (e.g. ``["scikit-learn==0.24.2", ...]``).
|
416
|
+
|
417
|
+
"""
|
418
|
+
raise_on_error = MLFLOW_REQUIREMENTS_INFERENCE_RAISE_ERRORS.get()
|
419
|
+
|
420
|
+
if timeout and is_windows():
|
421
|
+
timeout = None
|
422
|
+
_logger.warning(
|
423
|
+
"On Windows, timeout is not supported for model requirement inference. Therefore, "
|
424
|
+
"the operation is not bound by a timeout and may hang indefinitely. If it hangs, "
|
425
|
+
"please consider specifying the signature manually."
|
426
|
+
)
|
427
|
+
|
428
|
+
try:
|
429
|
+
if timeout:
|
430
|
+
with run_with_timeout(timeout):
|
431
|
+
return _infer_requirements(
|
432
|
+
model_uri, flavor, raise_on_error=raise_on_error, extra_env_vars=extra_env_vars
|
433
|
+
)
|
434
|
+
else:
|
435
|
+
return _infer_requirements(
|
436
|
+
model_uri, flavor, raise_on_error=raise_on_error, extra_env_vars=extra_env_vars
|
437
|
+
)
|
438
|
+
except Exception as e:
|
439
|
+
if raise_on_error or (fallback is None):
|
440
|
+
raise
|
441
|
+
|
442
|
+
if isinstance(e, MlflowTimeoutError):
|
443
|
+
msg = (
|
444
|
+
"Attempted to infer pip requirements for the saved model or pipeline but the "
|
445
|
+
f"operation timed out in {timeout} seconds. Fall back to return {fallback}. "
|
446
|
+
"You can specify a different timeout by setting the environment variable "
|
447
|
+
f"{MLFLOW_INPUT_EXAMPLE_INFERENCE_TIMEOUT}."
|
448
|
+
)
|
449
|
+
else:
|
450
|
+
msg = _INFER_PIP_REQUIREMENTS_GENERAL_ERROR_MESSAGE.format(
|
451
|
+
model_uri=model_uri, flavor=flavor, fallback=fallback
|
452
|
+
)
|
453
|
+
_logger.warning(msg)
|
454
|
+
_logger.debug("", exc_info=True)
|
455
|
+
return fallback
|
456
|
+
|
457
|
+
|
458
|
+
def _get_uv_options_for_databricks() -> tuple[list[str], dict[str, str]]:
|
459
|
+
"""
|
460
|
+
Retrieves the predefined secrets to configure `pip` for Databricks, and converts them into
|
461
|
+
command-line arguments and environment variables for `uv`.
|
462
|
+
|
463
|
+
References:
|
464
|
+
- https://docs.databricks.com/aws/en/compute/serverless/dependencies#predefined-secret-scope-name
|
465
|
+
- https://docs.astral.sh/uv/configuration/environment/#environment-variables
|
466
|
+
"""
|
467
|
+
from mlflow.utils.databricks_utils import (
|
468
|
+
_get_dbutils,
|
469
|
+
_NoDbutilsError,
|
470
|
+
is_in_databricks_runtime,
|
471
|
+
)
|
472
|
+
|
473
|
+
if not is_in_databricks_runtime():
|
474
|
+
return [], {}
|
475
|
+
|
476
|
+
try:
|
477
|
+
dbutils = _get_dbutils()
|
478
|
+
except _NoDbutilsError:
|
479
|
+
return [], {}
|
480
|
+
|
481
|
+
def get_secret(key: str) -> Optional[str]:
|
482
|
+
"""
|
483
|
+
Retrieves a secret from the Databricks secrets scope.
|
484
|
+
"""
|
485
|
+
try:
|
486
|
+
return dbutils.secrets.get(scope="databricks-package-management", key=key)
|
487
|
+
except Exception as e:
|
488
|
+
_logger.debug(f"Failed to fetch secret '{key}': {e}")
|
489
|
+
return None
|
490
|
+
|
491
|
+
args: list[str] = []
|
492
|
+
if url := get_secret("pip-index-url"):
|
493
|
+
args.append(f"--index-url={url}")
|
494
|
+
|
495
|
+
if urls := get_secret("pip-extra-index-urls"):
|
496
|
+
args.append(f"--extra-index-url={urls}")
|
497
|
+
|
498
|
+
# There is no command-line option for SSL_CERT_FILE in `uv`.
|
499
|
+
envs: dict[str, str] = {}
|
500
|
+
if cert := get_secret("pip-cert"):
|
501
|
+
envs["SSL_CERT_FILE"] = cert
|
502
|
+
|
503
|
+
_logger.debug(f"uv arguments and environment variables: {args}, {envs}")
|
504
|
+
return args, envs
|
505
|
+
|
506
|
+
|
507
|
+
def _lock_requirements(
|
508
|
+
requirements: list[str], constraints: Optional[list[str]] = None
|
509
|
+
) -> Optional[list[str]]:
|
510
|
+
"""
|
511
|
+
Locks the given requirements using `uv`. Returns the locked requirements when the locking is
|
512
|
+
performed successfully, otherwise returns None.
|
513
|
+
"""
|
514
|
+
if not MLFLOW_LOCK_MODEL_DEPENDENCIES.get():
|
515
|
+
return None
|
516
|
+
|
517
|
+
uv_bin = shutil.which("uv")
|
518
|
+
if uv_bin is None:
|
519
|
+
_logger.debug("`uv` binary not found. Skipping locking requirements.")
|
520
|
+
return None
|
521
|
+
|
522
|
+
_logger.info("Locking requirements...")
|
523
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
524
|
+
tmp_dir_path = pathlib.Path(tmp_dir)
|
525
|
+
in_file = tmp_dir_path / "requirements.in"
|
526
|
+
in_file.write_text("\n".join(requirements))
|
527
|
+
out_file = tmp_dir_path / "requirements.out"
|
528
|
+
constraints_opt: list[str] = []
|
529
|
+
if constraints:
|
530
|
+
constraints_file = tmp_dir_path / "constraints.txt"
|
531
|
+
constraints_file.write_text("\n".join(constraints))
|
532
|
+
constraints_opt = [f"--constraints={constraints_file}"]
|
533
|
+
try:
|
534
|
+
uv_options, uv_envs = _get_uv_options_for_databricks()
|
535
|
+
out = subprocess.check_output(
|
536
|
+
[
|
537
|
+
uv_bin,
|
538
|
+
"pip",
|
539
|
+
"compile",
|
540
|
+
"--color=never",
|
541
|
+
"--universal",
|
542
|
+
"--no-annotate",
|
543
|
+
"--no-header",
|
544
|
+
f"--python-version={PYTHON_VERSION}",
|
545
|
+
f"--output-file={out_file}",
|
546
|
+
*uv_options,
|
547
|
+
*constraints_opt,
|
548
|
+
in_file,
|
549
|
+
],
|
550
|
+
stderr=subprocess.STDOUT,
|
551
|
+
env=os.environ.copy() | uv_envs,
|
552
|
+
text=True,
|
553
|
+
)
|
554
|
+
_logger.debug(f"Successfully compiled requirements with `uv`:\n{out}")
|
555
|
+
except subprocess.CalledProcessError as e:
|
556
|
+
_logger.warning(f"Failed to lock requirements:\n{e.output}")
|
557
|
+
return None
|
558
|
+
|
559
|
+
return [
|
560
|
+
"# Original requirements",
|
561
|
+
*(f"# {l}" for l in requirements), # Preserve original requirements as comments
|
562
|
+
"#",
|
563
|
+
"# Locked requirements",
|
564
|
+
*out_file.read_text().splitlines(),
|
565
|
+
]
|
566
|
+
|
567
|
+
|
568
|
+
def _validate_env_arguments(conda_env, pip_requirements, extra_pip_requirements):
|
569
|
+
"""
|
570
|
+
Validates that only one or none of `conda_env`, `pip_requirements`, and
|
571
|
+
`extra_pip_requirements` is specified.
|
572
|
+
"""
|
573
|
+
args = [
|
574
|
+
conda_env,
|
575
|
+
pip_requirements,
|
576
|
+
extra_pip_requirements,
|
577
|
+
]
|
578
|
+
specified = [arg for arg in args if arg is not None]
|
579
|
+
if len(specified) > 1:
|
580
|
+
raise ValueError(
|
581
|
+
"Only one of `conda_env`, `pip_requirements`, and "
|
582
|
+
"`extra_pip_requirements` can be specified"
|
583
|
+
)
|
584
|
+
|
585
|
+
|
586
|
+
# PIP requirement parser inspired from https://github.com/pypa/pip/blob/b392833a0f1cff1bbee1ac6dbe0270cccdd0c11f/src/pip/_internal/req/req_file.py#L400
|
587
|
+
def _get_pip_requirement_specifier(requirement_string):
|
588
|
+
tokens = requirement_string.split(" ")
|
589
|
+
for idx, token in enumerate(tokens):
|
590
|
+
if token.startswith("-"):
|
591
|
+
return " ".join(tokens[:idx])
|
592
|
+
return requirement_string
|
593
|
+
|
594
|
+
|
595
|
+
def _is_mlflow_requirement(requirement_string):
|
596
|
+
"""
|
597
|
+
Returns True if `requirement_string` represents a requirement for mlflow (e.g. 'mlflow==1.2.3').
|
598
|
+
"""
|
599
|
+
# "/opt/mlflow" is the path where we mount the mlflow source code in the Docker container
|
600
|
+
# when running tests.
|
601
|
+
if _MLFLOW_TESTING.get() and requirement_string == "/opt/mlflow":
|
602
|
+
return True
|
603
|
+
|
604
|
+
try:
|
605
|
+
# `Requirement` throws an `InvalidRequirement` exception if `requirement_string` doesn't
|
606
|
+
# conform to PEP 508 (https://www.python.org/dev/peps/pep-0508).
|
607
|
+
return Requirement(requirement_string).name.lower() in ["mlflow", "mlflow-skinny"]
|
608
|
+
except InvalidRequirement:
|
609
|
+
# A local file path or URL falls into this branch.
|
610
|
+
|
611
|
+
# `Requirement` throws an `InvalidRequirement` exception if `requirement_string` contains
|
612
|
+
# per-requirement options (ex: package hashes)
|
613
|
+
# GitHub issue: https://github.com/pypa/packaging/issues/488
|
614
|
+
# Per-requirement-option spec: https://pip.pypa.io/en/stable/reference/requirements-file-format/#per-requirement-options
|
615
|
+
requirement_specifier = _get_pip_requirement_specifier(requirement_string)
|
616
|
+
try:
|
617
|
+
# Try again with the per-requirement options removed
|
618
|
+
return Requirement(requirement_specifier).name.lower() == "mlflow"
|
619
|
+
except InvalidRequirement:
|
620
|
+
# Support defining branch dependencies for local builds or direct GitHub builds
|
621
|
+
# from source.
|
622
|
+
# Example: mlflow @ git+https://github.com/mlflow/mlflow@branch_2.0
|
623
|
+
repository_matches = ["/mlflow", "mlflow@git"]
|
624
|
+
|
625
|
+
return any(
|
626
|
+
match in requirement_string.replace(" ", "").lower() for match in repository_matches
|
627
|
+
)
|
628
|
+
|
629
|
+
|
630
|
+
def _generate_mlflow_version_pinning() -> str:
|
631
|
+
"""Returns a pinned requirement for the current MLflow version (e.g., "mlflow==3.2.1").
|
632
|
+
|
633
|
+
Returns:
|
634
|
+
A pinned requirement for the current MLflow version.
|
635
|
+
|
636
|
+
"""
|
637
|
+
if _MLFLOW_TESTING.get():
|
638
|
+
# The local PyPI server should be running. It serves a wheel for the current MLflow version.
|
639
|
+
return f"mlflow=={VERSION}"
|
640
|
+
|
641
|
+
version = Version(VERSION)
|
642
|
+
if not version.is_devrelease:
|
643
|
+
# mlflow is installed from PyPI.
|
644
|
+
return f"mlflow=={VERSION}"
|
645
|
+
|
646
|
+
# We reach here when mlflow is installed from the source outside of the MLflow CI environment
|
647
|
+
# (e.g., Databricks notebook).
|
648
|
+
|
649
|
+
# mlflow installed from the source for development purposes. A dev version (e.g., 2.8.1.dev0)
|
650
|
+
# is always a micro-version ahead of the latest release (unless it's manually modified)
|
651
|
+
# and can't be installed from PyPI. We therefore subtract 1 from the micro version when running
|
652
|
+
# tests.
|
653
|
+
return f"mlflow=={version.major}.{version.minor}.{version.micro - 1}"
|
654
|
+
|
655
|
+
|
656
|
+
def _contains_mlflow_requirement(requirements):
|
657
|
+
"""
|
658
|
+
Returns True if `requirements` contains a requirement for mlflow (e.g. 'mlflow==1.2.3').
|
659
|
+
"""
|
660
|
+
return any(map(_is_mlflow_requirement, requirements))
|
661
|
+
|
662
|
+
|
663
|
+
def _process_pip_requirements(
|
664
|
+
default_pip_requirements, pip_requirements=None, extra_pip_requirements=None
|
665
|
+
):
|
666
|
+
"""
|
667
|
+
Processes `pip_requirements` and `extra_pip_requirements` passed to `mlflow.*.save_model` or
|
668
|
+
`mlflow.*.log_model`, and returns a tuple of (conda_env, pip_requirements, pip_constraints).
|
669
|
+
"""
|
670
|
+
constraints = []
|
671
|
+
if pip_requirements is not None:
|
672
|
+
pip_reqs, constraints = _parse_pip_requirements(pip_requirements)
|
673
|
+
elif extra_pip_requirements is not None:
|
674
|
+
extra_pip_requirements, constraints = _parse_pip_requirements(extra_pip_requirements)
|
675
|
+
pip_reqs = default_pip_requirements + extra_pip_requirements
|
676
|
+
else:
|
677
|
+
pip_reqs = default_pip_requirements
|
678
|
+
|
679
|
+
if not _contains_mlflow_requirement(pip_reqs):
|
680
|
+
pip_reqs.insert(0, _generate_mlflow_version_pinning())
|
681
|
+
|
682
|
+
sanitized_pip_reqs = _deduplicate_requirements(pip_reqs)
|
683
|
+
sanitized_pip_reqs = _remove_incompatible_requirements(sanitized_pip_reqs)
|
684
|
+
|
685
|
+
# Check if pip requirements contain incompatible version with the current environment
|
686
|
+
warn_dependency_requirement_mismatches(sanitized_pip_reqs)
|
687
|
+
|
688
|
+
if locked_requirements := _lock_requirements(sanitized_pip_reqs, constraints):
|
689
|
+
# Locking requirements was performed successfully
|
690
|
+
sanitized_pip_reqs = locked_requirements
|
691
|
+
else:
|
692
|
+
# Locking requirements was skipped or failed
|
693
|
+
if constraints:
|
694
|
+
sanitized_pip_reqs.append(f"-c {_CONSTRAINTS_FILE_NAME}")
|
695
|
+
|
696
|
+
# Set `install_mlflow` to False because `pip_reqs` already contains `mlflow`
|
697
|
+
conda_env = _mlflow_conda_env(additional_pip_deps=sanitized_pip_reqs, install_mlflow=False)
|
698
|
+
return conda_env, sanitized_pip_reqs, constraints
|
699
|
+
|
700
|
+
|
701
|
+
def _deduplicate_requirements(requirements):
|
702
|
+
"""
|
703
|
+
De-duplicates a list of pip package requirements, handling complex scenarios such as merging
|
704
|
+
extras and combining version constraints.
|
705
|
+
|
706
|
+
This function processes a list of pip package requirements and de-duplicates them. It handles
|
707
|
+
standard PyPI packages and non-standard requirements (like URLs or local paths). The function
|
708
|
+
merges extras and combines version constraints for duplicate packages. The most restrictive
|
709
|
+
version specifications or the ones with extras are prioritized. If incompatible version
|
710
|
+
constraints are detected, it raises an MlflowException.
|
711
|
+
|
712
|
+
Args:
|
713
|
+
requirements (list of str): A list of pip package requirement strings.
|
714
|
+
|
715
|
+
Returns:
|
716
|
+
list of str: A deduplicated list of pip package requirements.
|
717
|
+
|
718
|
+
Raises:
|
719
|
+
MlflowException: If incompatible version constraints are detected among the provided
|
720
|
+
requirements.
|
721
|
+
|
722
|
+
Examples:
|
723
|
+
- Input: ["packageA", "packageA==1.0"]
|
724
|
+
Output: ["packageA==1.0"]
|
725
|
+
|
726
|
+
- Input: ["packageX>1.0", "packageX[extras]", "packageX<2.0"]
|
727
|
+
Output: ["packageX[extras]>1.0,<2.0"]
|
728
|
+
|
729
|
+
- Input: ["markdown[extra1]>=3.5.1", "markdown[extra2]<4", "markdown"]
|
730
|
+
Output: ["markdown[extra1,extra2]>=3.5.1,<4"]
|
731
|
+
|
732
|
+
- Input: ["scikit-learn==1.1", "scikit-learn<1"]
|
733
|
+
Raises MlflowException indicating incompatible versions.
|
734
|
+
|
735
|
+
Note:
|
736
|
+
- Non-standard requirements (like URLs or file paths) are included as-is.
|
737
|
+
- If a requirement appears multiple times with different sets of extras, they are merged.
|
738
|
+
- The function uses `_validate_version_constraints` to check for incompatible version
|
739
|
+
constraints by doing a dry-run pip install of a requirements collection.
|
740
|
+
"""
|
741
|
+
deduped_reqs = {}
|
742
|
+
|
743
|
+
for req in requirements:
|
744
|
+
try:
|
745
|
+
parsed_req = Requirement(req)
|
746
|
+
base_pkg = parsed_req.name
|
747
|
+
|
748
|
+
existing_req = deduped_reqs.get(base_pkg)
|
749
|
+
|
750
|
+
if not existing_req:
|
751
|
+
deduped_reqs[base_pkg] = parsed_req
|
752
|
+
else:
|
753
|
+
# Verify that there are not unresolvable constraints applied if set and combine
|
754
|
+
# if possible
|
755
|
+
if (
|
756
|
+
existing_req.specifier
|
757
|
+
and parsed_req.specifier
|
758
|
+
and existing_req.specifier != parsed_req.specifier
|
759
|
+
):
|
760
|
+
_validate_version_constraints([str(existing_req), req])
|
761
|
+
parsed_req.specifier = ",".join(
|
762
|
+
[str(existing_req.specifier), str(parsed_req.specifier)]
|
763
|
+
)
|
764
|
+
|
765
|
+
# Preserve existing specifiers
|
766
|
+
if existing_req.specifier and not parsed_req.specifier:
|
767
|
+
parsed_req.specifier = existing_req.specifier
|
768
|
+
|
769
|
+
# Combine and apply extras if specified
|
770
|
+
if (
|
771
|
+
existing_req.extras
|
772
|
+
and parsed_req.extras
|
773
|
+
and existing_req.extras != parsed_req.extras
|
774
|
+
):
|
775
|
+
parsed_req.extras = sorted(set(existing_req.extras).union(parsed_req.extras))
|
776
|
+
elif existing_req.extras and not parsed_req.extras:
|
777
|
+
parsed_req.extras = existing_req.extras
|
778
|
+
|
779
|
+
deduped_reqs[base_pkg] = parsed_req
|
780
|
+
|
781
|
+
except InvalidRequirement:
|
782
|
+
# Include non-standard package strings as-is
|
783
|
+
if req not in deduped_reqs:
|
784
|
+
deduped_reqs[req] = req
|
785
|
+
return [str(req) for req in deduped_reqs.values()]
|
786
|
+
|
787
|
+
|
788
|
+
def _parse_requirement_name(req: str) -> str:
|
789
|
+
try:
|
790
|
+
return Requirement(req).name
|
791
|
+
except InvalidRequirement:
|
792
|
+
return req
|
793
|
+
|
794
|
+
|
795
|
+
def _remove_incompatible_requirements(requirements: list[str]) -> list[str]:
|
796
|
+
req_names = {_parse_requirement_name(req) for req in requirements}
|
797
|
+
if "databricks-connect" in req_names and req_names.intersection({"pyspark", "pyspark-connect"}):
|
798
|
+
_logger.debug(
|
799
|
+
"Found incompatible requirements: 'databricks-connect' with 'pyspark' or "
|
800
|
+
"'pyspark-connect'. Removing 'pyspark' or 'pyspark-connect' from the requirements."
|
801
|
+
)
|
802
|
+
requirements = [
|
803
|
+
req
|
804
|
+
for req in requirements
|
805
|
+
if _parse_requirement_name(req) not in ["pyspark", "pyspark-connect"]
|
806
|
+
]
|
807
|
+
return requirements
|
808
|
+
|
809
|
+
|
810
|
+
def _validate_version_constraints(requirements):
|
811
|
+
"""
|
812
|
+
Validates the version constraints of given Python package requirements using pip's resolver with
|
813
|
+
the `--dry-run` option enabled that performs validation only (will not install packages).
|
814
|
+
|
815
|
+
This function writes the requirements to a temporary file and then attempts to resolve
|
816
|
+
them using pip's `--dry-run` install option. If any version conflicts are detected, it
|
817
|
+
raises an MlflowException with details of the conflict.
|
818
|
+
|
819
|
+
Args:
|
820
|
+
requirements (list of str): A list of package requirements (e.g., `["pandas>=1.15",
|
821
|
+
"pandas<2"]`).
|
822
|
+
|
823
|
+
Raises:
|
824
|
+
MlflowException: If any version conflicts are detected among the provided requirements.
|
825
|
+
|
826
|
+
Returns:
|
827
|
+
None: This function does not return anything. It either completes successfully or raises
|
828
|
+
an MlflowException.
|
829
|
+
|
830
|
+
Example:
|
831
|
+
_validate_version_constraints(["tensorflow<2.0", "tensorflow>2.3"])
|
832
|
+
# This will raise an exception due to boundary validity.
|
833
|
+
"""
|
834
|
+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp_file:
|
835
|
+
tmp_file.write("\n".join(requirements))
|
836
|
+
tmp_file_name = tmp_file.name
|
837
|
+
|
838
|
+
try:
|
839
|
+
subprocess.run(
|
840
|
+
[sys.executable, "-m", "pip", "install", "--dry-run", "-r", tmp_file_name],
|
841
|
+
check=True,
|
842
|
+
capture_output=True,
|
843
|
+
)
|
844
|
+
except subprocess.CalledProcessError as e:
|
845
|
+
raise MlflowException.invalid_parameter_value(
|
846
|
+
"The specified requirements versions are incompatible. Detected "
|
847
|
+
f"conflicts: \n{e.stderr.decode()}"
|
848
|
+
)
|
849
|
+
finally:
|
850
|
+
os.remove(tmp_file_name)
|
851
|
+
|
852
|
+
|
853
|
+
def _process_conda_env(conda_env):
|
854
|
+
"""
|
855
|
+
Processes `conda_env` passed to `mlflow.*.save_model` or `mlflow.*.log_model`, and returns
|
856
|
+
a tuple of (conda_env, pip_requirements, pip_constraints).
|
857
|
+
"""
|
858
|
+
if isinstance(conda_env, str):
|
859
|
+
with open(conda_env) as f:
|
860
|
+
conda_env = yaml.safe_load(f)
|
861
|
+
elif not isinstance(conda_env, dict):
|
862
|
+
raise TypeError(
|
863
|
+
"Expected a string path to a conda env yaml file or a `dict` representing a conda env, "
|
864
|
+
f"but got `{type(conda_env).__name__}`"
|
865
|
+
)
|
866
|
+
|
867
|
+
# User-specified `conda_env` may contain requirements/constraints file references
|
868
|
+
pip_reqs = _get_pip_deps(conda_env)
|
869
|
+
pip_reqs, constraints = _parse_pip_requirements(pip_reqs)
|
870
|
+
if not _contains_mlflow_requirement(pip_reqs):
|
871
|
+
pip_reqs.insert(0, _generate_mlflow_version_pinning())
|
872
|
+
|
873
|
+
# Check if pip requirements contain incompatible version with the current environment
|
874
|
+
warn_dependency_requirement_mismatches(pip_reqs)
|
875
|
+
|
876
|
+
if constraints:
|
877
|
+
pip_reqs.append(f"-c {_CONSTRAINTS_FILE_NAME}")
|
878
|
+
|
879
|
+
conda_env = _overwrite_pip_deps(conda_env, pip_reqs)
|
880
|
+
return conda_env, pip_reqs, constraints
|
881
|
+
|
882
|
+
|
883
|
+
def _get_mlflow_env_name(s):
|
884
|
+
"""Creates an environment name for an MLflow model by hashing the given string.
|
885
|
+
|
886
|
+
Args:
|
887
|
+
s: String to hash (e.g. the content of `conda.yaml`).
|
888
|
+
|
889
|
+
Returns:
|
890
|
+
String in the form of "mlflow-{hash}"
|
891
|
+
(e.g. "mlflow-da39a3ee5e6b4b0d3255bfef95601890afd80709")
|
892
|
+
|
893
|
+
"""
|
894
|
+
return "mlflow-" + hashlib.sha1(s.encode("utf-8"), usedforsecurity=False).hexdigest()
|
895
|
+
|
896
|
+
|
897
|
+
def _get_pip_install_mlflow():
|
898
|
+
"""
|
899
|
+
Returns a command to pip-install mlflow. If the MLFLOW_HOME environment variable exists,
|
900
|
+
returns "pip install -e {MLFLOW_HOME} 1>&2", otherwise
|
901
|
+
"pip install mlflow=={mlflow.__version__} 1>&2".
|
902
|
+
"""
|
903
|
+
mlflow_home = os.getenv("MLFLOW_HOME")
|
904
|
+
if mlflow_home: # dev version
|
905
|
+
return f"pip install -e {mlflow_home} 1>&2"
|
906
|
+
else:
|
907
|
+
return f"pip install mlflow=={VERSION} 1>&2"
|
908
|
+
|
909
|
+
|
910
|
+
def _get_requirements_from_file(
|
911
|
+
file_path: pathlib.Path,
|
912
|
+
) -> list[Requirement]:
|
913
|
+
data = file_path.read_text()
|
914
|
+
if file_path.name == _CONDA_ENV_FILE_NAME:
|
915
|
+
conda_env = yaml.safe_load(data)
|
916
|
+
reqs = _get_pip_deps(conda_env)
|
917
|
+
else:
|
918
|
+
reqs = data.splitlines()
|
919
|
+
return [Requirement(req) for req in reqs if req]
|
920
|
+
|
921
|
+
|
922
|
+
def _write_requirements_to_file(
|
923
|
+
file_path: pathlib.Path,
|
924
|
+
new_reqs: list[str],
|
925
|
+
) -> None:
|
926
|
+
if file_path.name == _CONDA_ENV_FILE_NAME:
|
927
|
+
conda_env = yaml.safe_load(file_path.read_text())
|
928
|
+
conda_env = _overwrite_pip_deps(conda_env, new_reqs)
|
929
|
+
with file_path.open("w") as file:
|
930
|
+
yaml.dump(conda_env, file)
|
931
|
+
else:
|
932
|
+
file_path.write_text("\n".join(new_reqs))
|
933
|
+
|
934
|
+
|
935
|
+
def _add_or_overwrite_requirements(
|
936
|
+
new_reqs: list[Requirement],
|
937
|
+
old_reqs: list[Requirement],
|
938
|
+
) -> list[str]:
|
939
|
+
deduped_new_reqs = _deduplicate_requirements([str(req) for req in new_reqs])
|
940
|
+
deduped_new_reqs = [Requirement(req) for req in deduped_new_reqs]
|
941
|
+
|
942
|
+
old_reqs_dict = {req.name: str(req) for req in old_reqs}
|
943
|
+
new_reqs_dict = {req.name: str(req) for req in deduped_new_reqs}
|
944
|
+
old_reqs_dict.update(new_reqs_dict)
|
945
|
+
return list(old_reqs_dict.values())
|
946
|
+
|
947
|
+
|
948
|
+
def _remove_requirements(
|
949
|
+
reqs_to_remove: list[Requirement],
|
950
|
+
old_reqs: list[Requirement],
|
951
|
+
) -> list[str]:
|
952
|
+
old_reqs_dict = {req.name: str(req) for req in old_reqs}
|
953
|
+
for req in reqs_to_remove:
|
954
|
+
if req.name not in old_reqs_dict:
|
955
|
+
_logger.warning(f'"{req.name}" not found in requirements, ignoring')
|
956
|
+
old_reqs_dict.pop(req.name, None)
|
957
|
+
return list(old_reqs_dict.values())
|
958
|
+
|
959
|
+
|
960
|
+
class Environment:
|
961
|
+
def __init__(self, activate_cmd, extra_env=None):
|
962
|
+
if not isinstance(activate_cmd, list):
|
963
|
+
activate_cmd = [activate_cmd]
|
964
|
+
self._activate_cmd = activate_cmd
|
965
|
+
self._extra_env = extra_env or {}
|
966
|
+
|
967
|
+
def get_activate_command(self):
|
968
|
+
return self._activate_cmd
|
969
|
+
|
970
|
+
def execute(
|
971
|
+
self,
|
972
|
+
command,
|
973
|
+
command_env=None,
|
974
|
+
preexec_fn=None,
|
975
|
+
capture_output=False,
|
976
|
+
stdout=None,
|
977
|
+
stderr=None,
|
978
|
+
stdin=None,
|
979
|
+
synchronous=True,
|
980
|
+
):
|
981
|
+
command_env = os.environ.copy() if command_env is None else deepcopy(command_env)
|
982
|
+
if is_in_databricks_runtime():
|
983
|
+
command_env.update(get_databricks_env_vars(get_tracking_uri()))
|
984
|
+
if is_databricks_connect():
|
985
|
+
command_env.update(_get_databricks_serverless_env_vars())
|
986
|
+
if exp_id := _get_experiment_id():
|
987
|
+
command_env[MLFLOW_EXPERIMENT_ID.name] = exp_id
|
988
|
+
if active_model_id := get_active_model_id():
|
989
|
+
command_env[_MLFLOW_ACTIVE_MODEL_ID.name] = active_model_id
|
990
|
+
command_env.update(self._extra_env)
|
991
|
+
if not isinstance(command, list):
|
992
|
+
command = [command]
|
993
|
+
|
994
|
+
separator = " && " if not is_windows() else " & "
|
995
|
+
|
996
|
+
command = separator.join(map(str, self._activate_cmd + command))
|
997
|
+
command = ["bash", "-c", command] if not is_windows() else ["cmd", "/c", command]
|
998
|
+
_logger.info("=== Running command '%s'", command)
|
999
|
+
return _exec_cmd(
|
1000
|
+
command,
|
1001
|
+
env=command_env,
|
1002
|
+
capture_output=capture_output,
|
1003
|
+
synchronous=synchronous,
|
1004
|
+
preexec_fn=preexec_fn,
|
1005
|
+
close_fds=True,
|
1006
|
+
stdout=stdout,
|
1007
|
+
stderr=stderr,
|
1008
|
+
stdin=stdin,
|
1009
|
+
)
|