diff --git a/mlem/cli/apply.py b/mlem/cli/apply.py index 522c392c..d7c7635f 100644 --- a/mlem/cli/apply.py +++ b/mlem/cli/apply.py @@ -86,18 +86,6 @@ def apply( ): """Apply a model to data. The result will be saved as a MLEM object to `output` if provided. Otherwise, it will be printed to `stdout`. - - Examples: - Apply local mlem model to local mlem data - $ mlem apply mymodel mydata --method predict --output myprediction - - Apply local mlem model to local data file - $ mlem apply mymodel data.csv --method predict --import --import-type pandas[csv] --output myprediction - - Apply a version of remote model to a version of remote data - $ mlem apply models/logreg --project https://github.com/iterative/example-mlem --rev main - data/test_x --data-project https://github.com/iterative/example-mlem --data-rev main - --method predict --output myprediction """ from mlem.api import apply @@ -145,10 +133,6 @@ def apply( help="""Apply a deployed-model (possibly remotely) to data. The results will be saved as a MLEM object to `output` if provided. Otherwise, it will be printed to `stdout`. - - Examples: - Apply hosted mlem model to local mlem data - $ mlem apply-remote http mydata -c host="0.0.0.0" -c port=8080 --output myprediction """, cls=mlem_group("runtime"), subcommand_metavar="client", diff --git a/mlem/cli/build.py b/mlem/cli/build.py index eb287cf0..35372e08 100644 --- a/mlem/cli/build.py +++ b/mlem/cli/build.py @@ -28,14 +28,6 @@ help=""" Build models into re-usable assets you can distribute and use in production, such as a Docker image or Python package. - - Examples: - Build docker image from model - $ mlem build mymodel docker -c server.type=fastapi -c image.name=myimage - - Create build docker_dir declaration and build it - $ mlem declare builder docker_dir -c server=fastapi -c target=build build_dock - $ mlem build mymodel --load build_dock """, cls=mlem_group("runtime", aliases=["export"]), subcommand_metavar="builder", diff --git a/mlem/cli/checkenv.py b/mlem/cli/checkenv.py index b2b1443c..75e45d6b 100644 --- a/mlem/cli/checkenv.py +++ b/mlem/cli/checkenv.py @@ -19,15 +19,7 @@ def checkenv( project: Optional[str] = option_project, rev: Optional[str] = option_rev, ): - """Check that current environment satisfies object requrements - - Examples: - Check local object - $ mlem checkenv mymodel - - Check remote object - $ mlem checkenv https://github.com/iterative/example-mlem/models/logreg - """ + """Check that current environment satisfies object requrements""" meta = load_meta(path, project, rev, follow_links=True, load_value=False) if isinstance(meta, (MlemModel, MlemData)): meta.checkenv() diff --git a/mlem/cli/clone.py b/mlem/cli/clone.py index 3ae9b1a2..58c7b280 100644 --- a/mlem/cli/clone.py +++ b/mlem/cli/clone.py @@ -24,13 +24,6 @@ def clone( ): """Copy a MLEM Object from `uri` and saves a copy of it to `target` path. - - Examples: - Copy remote model to local directory - $ mlem clone models/logreg --project https://github.com/iterative/example-mlem --rev main mymodel - - Copy remote model to remote MLEM project - $ mlem clone models/logreg --project https://github.com/iterative/example-mlem --rev main mymodel --tp s3://mybucket/mymodel """ from mlem.api.commands import clone diff --git a/mlem/cli/config.py b/mlem/cli/config.py index c5edc01b..ef8e452c 100644 --- a/mlem/cli/config.py +++ b/mlem/cli/config.py @@ -33,8 +33,7 @@ def config_set( ): """Set configuration value - Examples: - $ mlem config set pandas.default_format csv + Documentation: """ fs, path = get_fs(project or "") project = find_project_root(path, fs=fs) @@ -70,9 +69,7 @@ def config_get( ): """Get configuration value - Examples: - $ mlem config get pandas.default_format - $ mlem config get pandas.default_format --project https://github.com/iterative/example-mlem/ + Documentation: """ fs, path = get_fs(project or "") project = find_project_root(path, fs=fs) diff --git a/mlem/cli/declare.py b/mlem/cli/declare.py index 87c82d16..92e01272 100644 --- a/mlem/cli/declare.py +++ b/mlem/cli/declare.py @@ -24,10 +24,6 @@ declare = Typer( name="declare", help="""Declares a new MLEM Object metafile from config args and config files. - - Examples: - Create heroku deployment - $ mlem declare env heroku production --api_key <...> """, cls=mlem_group("object"), subcommand_metavar="subtype", diff --git a/mlem/cli/deployment.py b/mlem/cli/deployment.py index 1dc901a5..06a2612e 100644 --- a/mlem/cli/deployment.py +++ b/mlem/cli/deployment.py @@ -51,16 +51,6 @@ def deploy_run( ): """Deploy a model to a target environment. Can use an existing deployment declaration or create a new one on-the-fly. - - Examples: - Create new deployment - $ mlem declare env heroku staging -c api_key=... - $ mlem deploy run service_name -m model -t staging -c name=my_service - - Deploy existing meta - $ mlem declare env heroku staging -c api_key=... - $ mlem declare deployment heroku service_name -c app_name=my_service -c model=model -c env=staging - $ mlem deploy run service_name """ from mlem.api.commands import deploy @@ -84,11 +74,7 @@ def deploy_remove( path: str = Argument(..., help="Path to deployment meta"), project: Optional[str] = option_project, ): - """Stop and destroy deployed instance. - - Examples: - $ mlem deployment remove service_name - """ + """Stop and destroy deployed instance.""" deploy_meta = load_meta(path, project=project, force_type=MlemDeployment) deploy_meta.remove() @@ -98,11 +84,7 @@ def deploy_status( path: str = Argument(..., help="Path to deployment meta"), project: Optional[str] = option_project, ): - """Print status of deployed service. - - Examples: - $ mlem deployment status service_name - """ + """Print status of deployed service.""" with no_echo(): deploy_meta = load_meta( path, project=project, force_type=MlemDeployment @@ -131,11 +113,7 @@ def deploy_wait( 0, "-t", "--times", help="Number of attempts. 0 -> indefinite" ), ): - """Wait for status of deployed service - - Examples: - $ mlem deployment status service_name - """ + """Wait for status of deployed service""" with no_echo(): deploy_meta = load_meta( path, project=project, force_type=MlemDeployment @@ -161,12 +139,7 @@ def deploy_apply( index: bool = option_index, json: bool = option_json, ): - """Apply a deployed model to data. - - Examples: - $ mlem deployment apply service_name - """ - + """Apply a deployed model to data.""" with set_echo(None if json else ...): deploy_meta = load_meta( path, project=project, rev=rev, force_type=MlemDeployment diff --git a/mlem/cli/dev.py b/mlem/cli/dev.py index 526bb7ca..00935b35 100644 --- a/mlem/cli/dev.py +++ b/mlem/cli/dev.py @@ -14,7 +14,10 @@ @dev.callback() def dev_callback(): - """Developer utility tools""" + """Developer utility tools + + Documentation: + """ @mlem_command(parent=dev, aliases=["fi"]) @@ -24,8 +27,7 @@ def find_implementations_diff( """Loads `root` module or package and finds implementations of MLEM base classes Shows differences between what was found and what is registered in entrypoints - Examples: - $ mlem dev fi + Documentation: """ exts = {e.entry for e in load_entrypoints().values()} impls = set(find_abc_implementations(root)[MLEM_ENTRY_POINT]) diff --git a/mlem/cli/import_object.py b/mlem/cli/import_object.py index 7eae2def..3070710b 100644 --- a/mlem/cli/import_object.py +++ b/mlem/cli/import_object.py @@ -29,18 +29,7 @@ def import_object( index: bool = option_index, external: bool = option_external, ): - """Create a `.mlem` metafile for a model or data in any file or directory. - - Examples: - Create MLEM data from local csv - $ mlem import data/data.csv data/imported_data --type pandas[csv] - - Create MLEM model from local pickle file - $ mlem import data/model.pkl data/imported_model - - Create MLEM model from remote pickle file - $ mlem import models/logreg --project https://github.com/iterative/example-mlem --rev no-dvc data/imported_model --type pickle - """ + """Create a `.mlem` metafile for a model or data in any file or directory.""" from mlem.api.commands import import_object import_object( diff --git a/mlem/cli/info.py b/mlem/cli/info.py index 7979ba24..59ea4f94 100644 --- a/mlem/cli/info.py +++ b/mlem/cli/info.py @@ -70,13 +70,7 @@ def ls( False, "-i", "--ignore-errors", help="Ignore corrupted objects" ), ): - """List MLEM objects inside a MLEM project. - - - Examples: - $ mlem list https://github.com/iterative/example-mlem - $ mlem list -t models - """ + """List MLEM objects inside a MLEM project.""" from mlem.api.commands import ls if type_filter == "all": @@ -123,13 +117,6 @@ def pretty_print( ): """Display all details about a specific MLEM Object from an existing MLEM project. - - Examples: - Print local object - $ mlem pprint mymodel - - Print remote object - $ mlem pprint https://github.com/iterative/example-mlem/models/logreg """ with set_echo(None if json else ...): meta = load_meta( diff --git a/mlem/cli/init.py b/mlem/cli/init.py index 3e48da87..0d53720d 100644 --- a/mlem/cli/init.py +++ b/mlem/cli/init.py @@ -12,13 +12,7 @@ def init( metavar=PATH_METAVAR, ) ): - """Initialize a MLEM project. - - Examples: - $ mlem init - $ mlem init some/local/path - $ mlem init s3://bucket/path/in/cloud - """ + """Initialize a MLEM project.""" from mlem.api.commands import init init(path) diff --git a/mlem/cli/link.py b/mlem/cli/link.py index 74b43885..bec6c89d 100644 --- a/mlem/cli/link.py +++ b/mlem/cli/link.py @@ -42,13 +42,6 @@ def link( ): """Create a link (read alias) for an existing MLEM Object, including from remote MLEM projects. - - Examples: - Add alias to local object - $ mlem link my_model latest - - Add remote object to your project without copy - $ mlem link models/logreg --source-project https://github.com/iteartive/example-mlem remote_model """ from mlem.api.commands import link diff --git a/mlem/cli/main.py b/mlem/cli/main.py index d69cb200..2282e431 100644 --- a/mlem/cli/main.py +++ b/mlem/cli/main.py @@ -28,7 +28,6 @@ LOAD_PARAM_NAME, NOT_SET, CallContext, - _extract_examples, _format_validation_error, get_extra_keys, ) @@ -58,13 +57,11 @@ class MlemMixin(Command): def __init__( self, *args, - examples: Optional[str], section: str = "other", aliases: List[str] = None, **kwargs, ): super().__init__(*args, **kwargs) - self.examples = examples self.section = section self.aliases = aliases self.rich_help_panel = section.capitalize() @@ -83,11 +80,17 @@ def get_help(self, ctx: Context) -> str: self.format_help(ctx, formatter) return formatter.getvalue().rstrip("\n") - def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: - super().format_epilog(ctx, formatter) - if self.examples: - with formatter.section("Examples"): - formatter.write(self.examples) + def _get_cmd_name_for_docs_link(self): + ctx = click.get_current_context() + return get_cmd_name(ctx, no_aliases=True, sep="/") + + @staticmethod + def _add_docs_link(help, cmd_name): + return ( + help + if "Documentation" in help + else f"{help}\n\nDocumentation: " + ) class MlemCommand( @@ -110,7 +113,6 @@ def __init__( ): self.dynamic_metavar = dynamic_metavar self.dynamic_options_generator = dynamic_options_generator - examples, help = _extract_examples(help) self._help = help self.lazy_help = lazy_help self.pass_from_parent = pass_from_parent @@ -118,7 +120,6 @@ def __init__( name=name, section=section, aliases=aliases, - examples=examples, help=help, **kwargs, ) @@ -185,9 +186,12 @@ def get_params(self, ctx) -> List["Parameter"]: @property def help(self): + cmd_name = self._get_cmd_name_for_docs_link() if self.lazy_help: - return self.lazy_help() - return self._help + if "/" in cmd_name: + cmd_name = cmd_name[: cmd_name.index("/")] + return self._add_docs_link(self.lazy_help(), cmd_name) + return self._add_docs_link(self._help, cmd_name) @help.setter def help(self, value): @@ -208,11 +212,9 @@ def __init__( help: str = None, **attrs: Any, ) -> None: - examples, help = _extract_examples(help) super().__init__( name=name, help=help, - examples=examples, aliases=aliases, section=section, commands=commands, @@ -270,6 +272,17 @@ def get_command(self, ctx: Context, cmd_name: str) -> Optional[Command]: return cmd return None + @property + def help(self): + cmd_name = self._get_cmd_name_for_docs_link() + if "/" in cmd_name: + cmd_name = cmd_name[: cmd_name.index("/")] + return self._add_docs_link(self._help, cmd_name) + + @help.setter + def help(self, value): + self._help = value + def mlem_group(section, aliases: Optional[List[str]] = None): class MlemGroupSection(MlemGroup): @@ -329,15 +342,8 @@ def mlem_callback( * Serialize any model trained in Python into ready-to-deploy format * Model lifecycle management using Git and GitOps principles * Provider-agnostic deployment - - Examples: - $ mlem init - $ mlem list https://github.com/iterative/example-mlem - $ mlem clone models/logreg --project https://github.com/iterative/example-mlem --rev main logreg - $ mlem link logreg latest - $ mlem apply latest https://github.com/iterative/example-mlem/data/test_x -o pred - $ mlem serve latest fastapi -c port=8001 - $ mlem build latest docker_dir -c target=build/ -c server.type=fastapi + \b + Documentation: """ if ctx.invoked_subcommand is None and show_version: with cli_echo(): @@ -349,12 +355,12 @@ def mlem_callback( ctx.obj = {"traceback": traceback or LOCAL_CONFIG.DEBUG} -def get_cmd_name(ctx: Context): +def get_cmd_name(ctx: Context, no_aliases=False, sep=" "): pieces = [] while ctx.parent is not None: - pieces.append(ctx.info_name) + pieces.append(ctx.command.name if no_aliases else ctx.info_name) ctx = ctx.parent - return " ".join(reversed(pieces)) + return sep.join(reversed(pieces)) def mlem_command( diff --git a/mlem/cli/serve.py b/mlem/cli/serve.py index 36badf66..8b1f8979 100644 --- a/mlem/cli/serve.py +++ b/mlem/cli/serve.py @@ -27,9 +27,6 @@ serve = Typer( name="serve", help="""Create an API from model methods using a server implementation. - - Examples: - $ mlem serve fastapi https://github.com/iterative/example-mlem/models/logreg """, cls=mlem_group("runtime"), subcommand_metavar="server", diff --git a/mlem/cli/types.py b/mlem/cli/types.py index 23a20e79..cba6b318 100644 --- a/mlem/cli/types.py +++ b/mlem/cli/types.py @@ -77,13 +77,6 @@ def list_types( ): """List different implementations available for a particular MLEM type. If a subtype is not provided, list all available MLEM types. - - Examples: - List ABCs - $ mlem types - - List available server implementations - $ mlem types server """ if abc is None: for at in MlemABC.abs_types.values(): diff --git a/mlem/cli/utils.py b/mlem/cli/utils.py index 6701c945..68cbb3ed 100644 --- a/mlem/cli/utils.py +++ b/mlem/cli/utils.py @@ -624,15 +624,3 @@ def config_arg( ) with wrap_build_error(subtype, model): return build_mlem_object(model, subtype, conf, file_conf, kwargs) - - -def _extract_examples( - help_str: Optional[str], -) -> Tuple[Optional[str], Optional[str]]: - if help_str is None: - return None, None - try: - examples = help_str.index("Examples:") - except ValueError: - return None, help_str - return help_str[examples + len("Examples:") + 1 :], help_str[:examples] diff --git a/mlem/contrib/pandas.py b/mlem/contrib/pandas.py index 80918822..9bd4c164 100644 --- a/mlem/contrib/pandas.py +++ b/mlem/contrib/pandas.py @@ -458,7 +458,9 @@ def read_pickle_with_unnamed(*args, **kwargs): def read_json_reset_index(*args, **kwargs): - return pd.read_json(*args, **kwargs).reset_index(drop=True) + return pd.read_json( # pylint: disable=no-member + *args, **kwargs + ).reset_index(drop=True) def read_html(*args, **kwargs): diff --git a/mlem/utils/module.py b/mlem/utils/module.py index b03b62af..142aa0e1 100644 --- a/mlem/utils/module.py +++ b/mlem/utils/module.py @@ -411,7 +411,10 @@ def wrapper(pickler: "RequirementAnalyzer", obj): else: pickler.save(o) - if is_from_installable_module(obj): + if ( + is_from_installable_module(obj) + or get_object_base_module(obj) is mlem + ): return f(pickler, obj) # to add from local imports inside user (non PIP package) code @@ -514,6 +517,7 @@ def _should_ignore(self, mod: ModuleType): or is_private_module(mod) or is_pseudo_module(mod) or is_builtin_module(mod) + or mod in self._modules ) def add_requirement(self, obj_or_module): @@ -533,6 +537,11 @@ def add_requirement(self, obj_or_module): module = obj_or_module if module is not None and not self._should_ignore(module): + base_module = get_base_module(module) + if is_installable_module(base_module): + if base_module in self._modules: + return + module = base_module self._modules.add(module) if is_local_module(module): # add imports of this module @@ -553,6 +562,8 @@ def save(self, obj, save_persistent_id=True): if id(obj) in self.seen or isinstance(obj, IGNORE_TYPES_REQ): return None self.seen.add(id(obj)) + if get_object_base_module(obj) in self._modules: + return None self.add_requirement(obj) try: return super().save(obj, save_persistent_id) diff --git a/setup.cfg b/setup.cfg index 0d10ac3b..a4d4d328 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,8 +5,9 @@ ignore = E266, # Too many leading '#' for block comment W503, # Line break occurred before a binary operator B008, # Do not perform function calls in argument defaults: conflicts with typer - P1, # unindexed parameters in the str.format, see: + P1, # unindexed parameters in the str.format, see: B902, # Invalid first argument 'cls' used for instance method. + B024, # ABCs without methods # https://pypi.org/project/flake8-string-format/ max_line_length = 79 max-complexity = 15 diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index c7de7522..90010244 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -1,9 +1,11 @@ import pytest -from click import Group -from typer.main import get_command_from_info, get_group_from_info +import requests +from click import Context, Group +from typer.main import get_command_from_info, get_group, get_group_from_info from mlem.cli import app from tests.cli.conftest import Runner +from tests.conftest import long def iter_group(group: Group, prefix=()): @@ -37,9 +39,13 @@ def app_cli_cmd(): def test_commands_help(app_cli_cmd): no_help = [] - for name, cli_cmd in app_cli_cmd: - if cli_cmd.help is None: - no_help.append(name) + group = get_group(app) + ctx = Context(group, info_name="mlem", help_option_names=["-h", "--help"]) + + with ctx: + for name, cli_cmd in app_cli_cmd: + if cli_cmd.help is None: + no_help.append(name) assert len(no_help) == 0, f"{no_help} cli commands do not have help!" @@ -52,18 +58,29 @@ def test_commands_args_help(app_cli_cmd): continue if arg.help is None: no_help.append(f"{name}:{arg.name}") - assert len(no_help) == 0, f"{no_help} cli commnad args do not have help!" - - -@pytest.mark.xfail # TODO do we need examples for everything? -def test_commands_examples(app_cli_cmd): - no_examples = [] - for name, cmd in app_cli_cmd: - if cmd.examples is None and not isinstance(cmd, Group): - no_examples.append(name) + assert len(no_help) == 0, f"{no_help} cli commands args do not have help!" + + +@pytest.mark.xfail +@long +def test_commands_docs_links(app_cli_cmd): + no_link = [] + link_broken = [] + for name, _cmd in app_cli_cmd: + result = Runner().invoke(name.split() + ["--help"]) + if result.output is None or "Documentation: <" not in result.output: + no_link.append(name) + else: + link = result.output.split("Documentation: <")[1].split(">")[0] + response = requests.get(link, timeout=5) + if response.status_code != 200: + link_broken.append(name) + assert ( + len(no_link) == 0 + ), f"{no_link} cli commands do not have documentation link!" assert ( - len(no_examples) == 0 - ), f"{no_examples} cli commnads do not have examples!" + len(link_broken) == 0 + ), f"{link_broken} cli commands have broken documentation links!" @pytest.mark.parametrize("cmd", ["--help", "-h"]) @@ -78,12 +95,7 @@ def test_help(runner: Runner, cmd): def test_cli_commands_help(runner: Runner, app_cli_cmd): for name, _ in app_cli_cmd: - result = runner.invoke(name + " --help") - assert result.exit_code == 0, ( - result.stdout, - result.stderr, - result.exception, - ) + runner.invoke(name + " --help", raise_on_error=True) def test_version(runner: Runner): diff --git a/tests/contrib/test_catboost.py b/tests/contrib/test_catboost.py index 51ad753a..ada91728 100644 --- a/tests/contrib/test_catboost.py +++ b/tests/contrib/test_catboost.py @@ -48,7 +48,7 @@ def test_catboost_model(catboost_model_fixture, pandas_data, tmpdir, request): ), ) - expected_requirements = {"catboost", "pandas", "numpy", "scipy"} + expected_requirements = {"catboost", "pandas"} reqs = set(cbmw.get_requirements().modules) assert all(r in reqs for r in expected_requirements) assert cbmw.model is catboost_model diff --git a/tests/contrib/test_lightgbm.py b/tests/contrib/test_lightgbm.py index 56ed8f7b..bd2e193a 100644 --- a/tests/contrib/test_lightgbm.py +++ b/tests/contrib/test_lightgbm.py @@ -195,7 +195,7 @@ def test_model__predict_not_dataset(model): @long def test_model__dump_load(tmpdir, model, data_np, local_fs): # pandas is not required, but if it is installed, it is imported by lightgbm - expected_requirements = {"lightgbm", "numpy", "scipy", "pandas"} + expected_requirements = {"lightgbm", "numpy"} assert set(model.get_requirements().modules) == expected_requirements artifacts = model.dump(LOCAL_STORAGE, tmpdir) diff --git a/tests/contrib/test_pandas.py b/tests/contrib/test_pandas.py index 3d1c770e..d8c258cd 100644 --- a/tests/contrib/test_pandas.py +++ b/tests/contrib/test_pandas.py @@ -617,7 +617,7 @@ def f(x): sig = Signature.from_method(f, auto_infer=True, x=data) - assert set(get_object_requirements(sig).modules) == {"pandas", "numpy"} + assert set(get_object_requirements(sig).modules) == {"pandas"} # Copyright 2019 Zyfra diff --git a/tests/contrib/test_sklearn.py b/tests/contrib/test_sklearn.py index 9673e62f..57dbde36 100644 --- a/tests/contrib/test_sklearn.py +++ b/tests/contrib/test_sklearn.py @@ -140,7 +140,7 @@ def test_model_type__dump_load(tmpdir, model, inp_data, request): def test_model_type_lgb__dump_load(tmpdir, lgbm_model, inp_data): model_type = ModelAnalyzer.analyze(lgbm_model, sample_data=inp_data) - expected_requirements = {"sklearn", "lightgbm", "pandas", "numpy", "scipy"} + expected_requirements = {"sklearn", "lightgbm", "numpy"} reqs = model_type.get_requirements().expanded assert set(reqs.modules) == expected_requirements assert reqs.of_type(UnixPackageRequirement) == [ @@ -164,11 +164,16 @@ def test_model_type_lgb__dump_load(tmpdir, lgbm_model, inp_data): ] -def test_pipeline_requirements(lgbm_model): +def test_pipeline_requirements(lgbm_model, inp_data): model = Pipeline(steps=[("model", lgbm_model)]) meta = MlemModel.from_obj(model) - expected_requirements = {"sklearn", "lightgbm", "pandas", "numpy", "scipy"} + expected_requirements = {"sklearn", "lightgbm"} + assert set(meta.requirements.modules) == expected_requirements + + meta = MlemModel.from_obj(model, sample_data=np.array(inp_data)) + + expected_requirements = {"sklearn", "lightgbm", "numpy"} assert set(meta.requirements.modules) == expected_requirements diff --git a/tests/contrib/test_xgboost.py b/tests/contrib/test_xgboost.py index 4b385196..0a2feb1f 100644 --- a/tests/contrib/test_xgboost.py +++ b/tests/contrib/test_xgboost.py @@ -132,8 +132,7 @@ def test_model__predict_not_dmatrix(model): @long def test_model__dump_load(tmpdir, model, dmatrix_np, local_fs): - # pandas is not required, but it is conditionally imported by some Booster methods - expected_requirements = {"xgboost", "numpy", "scipy", "pandas"} + expected_requirements = {"xgboost", "numpy"} assert set(model.get_requirements().modules) == expected_requirements artifacts = model.dump(LOCAL_STORAGE, tmpdir)