11#!/usr/bin/env python
2- # -*- coding: utf-8; -*-
32
4- # Copyright (c) 2020, 2023 Oracle and/or its affiliates.
3+ # Copyright (c) 2020, 2026 Oracle and/or its affiliates.
54# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
65
76import warnings
2120import json
2221import os
2322import re
24- import git
2523import shutil
2624import subprocess
2725import sys
2826import textwrap
2927import uuid
30- import python_jsonschema_objects as pjs
3128from enum import Enum
3229from pathlib import Path
3330from typing import Dict , Optional , Union
3431
35- import ads .dataset .factory as factory
3632import fsspec
33+ import git
3734import numpy as np
3835import oci .data_science
3936import oci .exceptions
4037import pandas as pd
41- import pkg_resources
38+ import python_jsonschema_objects as pjs
4239import yaml
40+ from oci .data_science .models import ModelProvenance
4341
44- from ads .common .decorator .runtime_dependency import (
45- runtime_dependency ,
46- OptionalDependency ,
47- )
48- from ads .common import logger , utils
4942from ads .common import auth as authutil
43+ from ads .common import logger , utils
5044from ads .common .data import ADSData
45+ from ads .common .decorator .deprecate import deprecated
5146from ads .common .error import ChangesNotCommitted
47+ from ads .common .object_storage_details import (
48+ InvalidObjectStoragePath ,
49+ ObjectStorageDetails ,
50+ )
51+ from ads .common .utils import DATA_SCHEMA_MAX_COL_NUM
52+ from ads .config import (
53+ JOB_RUN_COMPARTMENT_OCID ,
54+ JOB_RUN_OCID ,
55+ NB_SESSION_COMPARTMENT_OCID ,
56+ NB_SESSION_OCID ,
57+ PROJECT_OCID ,
58+ )
59+ from ads .dataset import factory
60+ from ads .feature_engineering .schema import DataSizeTooWide , Schema , SchemaSizeTooLarge
61+ from ads .model .common .utils import fetch_manifest_from_conda_location
62+ from ads .model .extractor .model_info_extractor_factory import ModelInfoExtractorFactory
5263from ads .model .model_introspect import (
5364 TEST_STATUS ,
5465 Introspectable ,
6677 ModelTaxonomyMetadata ,
6778 UseCaseType ,
6879)
69- from ads .common .object_storage_details import (
70- InvalidObjectStoragePath ,
71- ObjectStorageDetails ,
72- )
73- from ads .common .utils import DATA_SCHEMA_MAX_COL_NUM
74- from ads .config import (
75- JOB_RUN_COMPARTMENT_OCID ,
76- JOB_RUN_OCID ,
77- NB_SESSION_COMPARTMENT_OCID ,
78- NB_SESSION_OCID ,
79- PROJECT_OCID ,
80- )
81- from ads .common .decorator .deprecate import deprecated
82- from ads .feature_engineering .schema import DataSizeTooWide , Schema , SchemaSizeTooLarge
83- from ads .model .extractor .model_info_extractor_factory import ModelInfoExtractorFactory
8480from ads .model .model_version_set import ModelVersionSet
85- from ads .model .common .utils import fetch_manifest_from_conda_location
86- from git import InvalidGitRepositoryError , Repo
87-
88- from oci .data_science .models import ModelProvenance
8981
9082try :
9183 from yaml import CDumper as dumper
92- from yaml import CLoader as loader
9384except :
9485 from yaml import Dumper as dumper
95- from yaml import Loader as loader
9686
9787MODEL_ARTIFACT_VERSION = "3.0"
9888INPUT_SCHEMA_FILE_NAME = "input_schema.json"
10292_COMPARTMENT_OCID = NB_SESSION_COMPARTMENT_OCID or JOB_RUN_COMPARTMENT_OCID
10393
10494
105- class InvalidDataType (Exception ): # pragma: no cover
95+ class InvalidDataType (Exception ): # pragma: no cover
10696 """Invalid Data Type."""
10797
10898 pass
@@ -119,7 +109,7 @@ class InvalidDataType(Exception): # pragma: no cover
119109"""
120110
121111
122- class ConflictStrategy ( object ) :
112+ class ConflictStrategy :
123113 IGNORE = "IGNORE"
124114 UPDATE = "UPDATE"
125115 CREATE = "CREATE"
@@ -305,7 +295,7 @@ def __fetch_training_env_details(self, training_info):
305295 os .path .join (os .path .expanduser ("~" ), "conda" , "config.yaml" )
306296 ):
307297 with open (
308- ( os .path .join (os .path .expanduser ("~" ), "conda" , "config.yaml" ) )
298+ os .path .join (os .path .expanduser ("~" ), "conda" , "config.yaml" )
309299 ) as conf :
310300 user_config = yaml .load (conf , Loader = yaml .FullLoader )
311301 pack_bucket = user_config ["bucket_info" ]["name" ]
@@ -327,7 +317,7 @@ def __fetch_training_env_details(self, training_info):
327317 if manifest_type == PACK_TYPE .USER_CUSTOM_PACK .value :
328318 if self .data_science_env :
329319 raise Exception (
330- f "For Published conda environments, assign the path of the environment in "
320+ "For Published conda environments, assign the path of the environment in "
331321 + "Object Storage to the `inference_conda_env` parameter and set the "
332322 + "parameter `data_science_env` to `False`."
333323 )
@@ -338,16 +328,15 @@ def __fetch_training_env_details(self, training_info):
338328 )
339329 if self .ignore_deployment_error :
340330 logger .warn (error_message )
341- else :
342- if not self .inference_conda_env :
343- logger .error (error_message )
344- logger .info (
345- "Provide a URI to the conda environment that you wish to use with the model "
346- "deployment service if you do not want to publish the current training environment."
347- )
348- raise Exception (
349- f"Could not resolve the path in the Object Storage for the conda environment: { conda_prefix } "
350- )
331+ elif not self .inference_conda_env :
332+ logger .error (error_message )
333+ logger .info (
334+ "Provide a URI to the conda environment that you wish to use with the model "
335+ "deployment service if you do not want to publish the current training environment."
336+ )
337+ raise Exception (
338+ f"Could not resolve the path in the Object Storage for the conda environment: { conda_prefix } "
339+ )
351340 else :
352341 logger .warn (
353342 f"Could not resolve the Object Storage destination of { conda_prefix } . Correct "
@@ -416,15 +405,15 @@ def _generate_empty_runtime_yaml(
416405 f"The inference conda environment is { inference_conda_env } and the Python version is { inference_python_version } ."
417406 )
418407 if inference_conda_env :
419- content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ][
420- "INFERENCE_ENV_SLUG "
421- ] = ""
422- content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ][
423- "INFERENCE_ENV_TYPE "
424- ] = ""
425- content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ][
426- "INFERENCE_ENV_PATH"
427- ] = inference_conda_env
408+ content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ]["INFERENCE_ENV_SLUG" ] = (
409+ ""
410+ )
411+ content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ]["INFERENCE_ENV_TYPE" ] = (
412+ ""
413+ )
414+ content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ]["INFERENCE_ENV_PATH" ] = (
415+ inference_conda_env
416+ )
428417 if inference_python_version :
429418 content ["MODEL_DEPLOYMENT" ]["INFERENCE_CONDA_ENV" ][
430419 "INFERENCE_PYTHON_VERSION"
@@ -511,7 +500,7 @@ def _generate_runtime_yaml(self, model_file_name="model.onnx"):
511500 if (
512501 not self .inference_conda_env
513502 and not self .data_science_env
514- and inference_info . INFERENCE_ENV_TYPE == PACK_TYPE . SERVICE_PACK . value
503+ and PACK_TYPE . SERVICE_PACK . value == inference_info . INFERENCE_ENV_TYPE
515504 and training_env_info .TRAINING_ENV_PATH == inference_info .INFERENCE_ENV_PATH
516505 ):
517506 error_message = (
@@ -526,7 +515,7 @@ def _generate_runtime_yaml(self, model_file_name="model.onnx"):
526515
527516 if not inference_info .INFERENCE_ENV_PATH and not self .inference_conda_env :
528517 error_message = (
529- f "The inference conda environment is missing. Set the `inference_conda_env` parameter "
518+ "The inference conda environment is missing. Set the `inference_conda_env` parameter "
530519 + "or publish the conda environment and run the `.prepare()` method."
531520 )
532521 if not self .ignore_deployment_error :
@@ -808,7 +797,7 @@ def save(
808797
809798 runtime_yaml_file = os .path .join (self .artifact_dir , "runtime.yaml" )
810799 if os .path .exists (runtime_yaml_file ):
811- with open (runtime_yaml_file , "r" ) as mfile :
800+ with open (runtime_yaml_file ) as mfile :
812801 runtime_prep_info = yaml .load (mfile , Loader = yaml .FullLoader )
813802 # runtime_info['pack-info'] = deployment_pack_info
814803 else :
@@ -982,8 +971,11 @@ def install_requirements(self, conflict_strategy=ConflictStrategy.IGNORE):
982971 IGNORE: Use the installed version in case of a conflict.
983972 UPDATE: Force update dependency to the version required by model artifact in case of conflict.
984973 """
985- importlib .reload (pkg_resources )
986- from pkg_resources import DistributionNotFound , VersionConflict
974+ from importlib .metadata import PackageNotFoundError
975+ from importlib .metadata import version as get_version
976+
977+ from packaging .markers import default_environment
978+ from packaging .requirements import Requirement
987979
988980 if self .version .split ("." )[0 ] not in ["0" , "1" ] and os .path .exists (
989981 Path (os .path .join (self .artifact_dir ), "requirements.txt" )
@@ -1010,19 +1002,43 @@ def install_requirements(self, conflict_strategy=ConflictStrategy.IGNORE):
10101002 )
10111003
10121004 version_conflicts = {}
1005+ env = default_environment ()
10131006 for requirement in requirements :
1007+ req_line = requirement .strip ()
1008+ if not req_line or req_line .startswith ("#" ):
1009+ continue
1010+ # Skip include or index options lines
1011+ if req_line .startswith (("-" , "--" )):
1012+ continue
1013+ try :
1014+ req = Requirement (req_line )
1015+ except Exception :
1016+ # If the requirement line cannot be parsed, attempt to install it as-is.
1017+ pip_install (req_line )
1018+ continue
1019+
1020+ # Evaluate environment markers, if any
1021+ if req .marker and not req .marker .evaluate (environment = env ):
1022+ continue
1023+
1024+ package_name = req .name
1025+ spec = req .specifier # SpecifierSet
1026+
10141027 try :
1015- pkg_resources .require (requirement )
1016- except VersionConflict as vc :
1028+ installed_version = get_version (package_name )
1029+ except PackageNotFoundError :
1030+ # Not installed; install the requirement as written
1031+ pip_install (req_line )
1032+ continue
1033+
1034+ if spec and installed_version not in spec :
10171035 if conflict_strategy == ConflictStrategy .UPDATE :
1018- pip_install ("%s%s" % ( vc . req . name , vc . req . specifier ) , "-U" )
1036+ pip_install (f" { package_name } { spec } " , "-U" )
10191037 elif conflict_strategy == ConflictStrategy .IGNORE :
1020- version_conflicts [
1021- "%s==%s" % (vc .dist .key , vc .dist .parsed_version )
1022- ] = "%s%s" % (vc .req .name , vc .req .specifier )
1023- except DistributionNotFound as dnf :
1024- pip_install (requirement )
1025- # distributions_not_found.add('%s%s' % (dnf.req.name, dnf.req.specifier))
1038+ version_conflicts [f"{ package_name } =={ installed_version } " ] = (
1039+ f"{ package_name } { spec } "
1040+ )
1041+
10261042 if len (version_conflicts ) > 0 :
10271043 print (
10281044 "\033 [93m"
@@ -1308,21 +1324,15 @@ def _populate_metadata_custom(self):
13081324 )
13091325 )
13101326 try :
1311- env_type = (
1312- self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_TYPE ._value
1313- )
1327+ env_type = self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_TYPE ._value
13141328 except :
13151329 env_type = None
13161330 try :
1317- slug_name = (
1318- self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_SLUG ._value
1319- )
1331+ slug_name = self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_SLUG ._value
13201332 except :
13211333 slug_name = None
13221334 try :
1323- env_path = (
1324- self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_PATH ._value
1325- )
1335+ env_path = self ._runtime_info .MODEL_DEPLOYMENT .INFERENCE_CONDA_ENV .INFERENCE_ENV_PATH ._value
13261336 except :
13271337 env_path = None
13281338
@@ -1467,7 +1477,6 @@ def _save_from_local_file(
14671477 self ._save_data_path (", " .join (oci_storage_paths ), data_type )
14681478
14691479 def _save_data_path (self , oci_storage_path , data_type ):
1470-
14711480 key = (
14721481 MetadataCustomKeys .TRAINING_DATASET
14731482 if data_type == "training"
@@ -1728,7 +1737,7 @@ def from_model_catalog(
17281737 return result_artifact
17291738
17301739
1731- class VersionConflictWarning ( object ) :
1740+ class VersionConflictWarning :
17321741 def __init__ (self , version_conflicts ):
17331742 self .version_conflicts = version_conflicts
17341743
0 commit comments