Skip to content

Commit 5062a1e

Browse files
use pipelinestep
1 parent c49a689 commit 5062a1e

4 files changed

Lines changed: 122 additions & 70 deletions

File tree

clarifai/cli/pipeline.py

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -93,61 +93,6 @@ def pipeline():
9393
"""Create and manage pipelines."""
9494

9595

96-
@pipeline.command('local-run')
97-
@click.argument("step_path", type=click.Path(exists=True), required=False, default=".")
98-
@click.option(
99-
'--mode',
100-
type=click.Choice(['container']),
101-
default='container',
102-
help='Execution mode. Currently only "container" is supported.',
103-
)
104-
@click.option(
105-
'--keep-image',
106-
is_flag=True,
107-
default=False,
108-
help='Keep the Docker image after the pipeline step finishes.',
109-
)
110-
def local_run(step_path, mode, keep_image):
111-
"""Run a pipeline step locally in a Docker container.
112-
113-
STEP_PATH: Path to the pipeline step directory (containing config.yaml,
114-
requirements.txt, and 1/pipeline_step.py). Defaults to current directory.
115-
116-
This reuses the same Docker build infrastructure as ``clarifai model serve
117-
--mode container`` but executes pipeline_step.py once and exits.
118-
"""
119-
from clarifai.runners.pipeline_steps.pipeline_run_locally import PipelineStepRunLocally
120-
121-
manager = PipelineStepRunLocally(step_path)
122-
123-
if not manager.is_docker_installed():
124-
raise click.ClickException("Docker is not installed.")
125-
126-
# Generate Dockerfile if missing
127-
manager.builder.create_dockerfile()
128-
129-
image_tag = manager._docker_hash()
130-
step_id = manager.config['pipeline_step']['id'].lower()
131-
image_name = f"{step_id}:{image_tag}"
132-
container_name = f"{step_id}-local-run"
133-
134-
if not manager.docker_image_exists(image_name):
135-
logger.info("Building Docker image...")
136-
manager.build_docker_image(image_name=image_name)
137-
138-
try:
139-
manager.run_pipeline_step_container(
140-
image_name=image_name,
141-
container_name=container_name,
142-
)
143-
finally:
144-
if manager.container_exists(container_name):
145-
manager.stop_docker_container(container_name)
146-
manager.remove_docker_container(container_name)
147-
if not keep_image:
148-
manager.remove_docker_image(image_name)
149-
150-
15196
@pipeline.command()
15297
@click.argument("path", type=click.Path(exists=True), required=False, default=".")
15398
@click.option(

clarifai/cli/pipeline_step.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,61 @@ def init(pipeline_step_path):
111111
logger.info("4. Implement your pipeline step logic in 1/pipeline_step.py")
112112

113113

114+
@pipeline_step.command('local-run')
115+
@click.argument("pipeline_step_path", type=click.Path(exists=True), required=False, default=".")
116+
@click.option(
117+
'--mode',
118+
type=click.Choice(['container']),
119+
default='container',
120+
help='Execution mode. Currently only "container" is supported.',
121+
)
122+
@click.option(
123+
'--keep-image',
124+
is_flag=True,
125+
default=False,
126+
help='Keep the Docker image after the pipeline step finishes.',
127+
)
128+
def local_run(pipeline_step_path, mode, keep_image):
129+
"""Run a pipeline step locally in a Docker container.
130+
131+
PIPELINE_STEP_PATH: Path to the pipeline step directory (containing config.yaml,
132+
requirements.txt, and 1/pipeline_step.py). Defaults to current directory.
133+
134+
This reuses the same Docker build infrastructure as ``clarifai model serve
135+
--mode container`` but executes pipeline_step.py once and exits.
136+
"""
137+
from clarifai.runners.pipeline_steps.pipeline_run_locally import PipelineStepRunLocally
138+
139+
manager = PipelineStepRunLocally(pipeline_step_path)
140+
141+
if not manager.is_docker_installed():
142+
raise click.ClickException("Docker is not installed.")
143+
144+
# Generate Dockerfile if missing
145+
manager.builder.create_dockerfile()
146+
147+
image_tag = manager._docker_hash()
148+
step_id = manager.config['pipeline_step']['id'].lower()
149+
image_name = f"{step_id}:{image_tag}"
150+
container_name = f"{step_id}-local-run"
151+
152+
if not manager.docker_image_exists(image_name):
153+
logger.info("Building Docker image...")
154+
manager.build_docker_image(image_name=image_name)
155+
156+
try:
157+
manager.run_pipeline_step_container(
158+
image_name=image_name,
159+
container_name=container_name,
160+
)
161+
finally:
162+
if manager.container_exists(container_name):
163+
manager.stop_docker_container(container_name)
164+
manager.remove_docker_container(container_name)
165+
if not keep_image:
166+
manager.remove_docker_image(image_name)
167+
168+
114169
@pipeline_step.command(['ls'])
115170
@click.option('--page_no', required=False, help='Page number to list.', default=1)
116171
@click.option('--per_page', required=False, help='Number of items per page.', default=16)

tests/cli/test_pipeline_step.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from click.testing import CliRunner
1010

11-
from clarifai.cli.pipeline_step import init, list, upload
11+
from clarifai.cli.pipeline_step import init, list, local_run, upload
1212

1313

1414
class TestPipelineStepInitCommand:
@@ -452,3 +452,55 @@ def test_list_command_help(self):
452452
assert '--pipeline_id' in result.output
453453
assert '--page_no' in result.output
454454
assert '--per_page' in result.output
455+
456+
def test_local_run_command_help(self):
457+
"""Test that local-run command shows helpful usage information."""
458+
runner = CliRunner()
459+
460+
result = runner.invoke(local_run, ['--help'])
461+
462+
assert result.exit_code == 0
463+
assert 'Run a pipeline step locally in a Docker container' in result.output
464+
assert '--mode' in result.output
465+
assert '--keep-image' in result.output
466+
assert 'PIPELINE_STEP_PATH' in result.output
467+
468+
469+
class TestPipelineStepLocalRunCommand:
470+
"""Test cases for the pipeline step local-run CLI command."""
471+
472+
def test_local_run_nonexistent_path(self):
473+
"""Test local-run with nonexistent path fails."""
474+
runner = CliRunner()
475+
476+
result = runner.invoke(local_run, ['nonexistent_path'])
477+
478+
assert result.exit_code != 0
479+
assert 'does not exist' in result.output
480+
481+
@patch(
482+
'clarifai.runners.pipeline_steps.pipeline_run_locally.PipelineStepRunLocally.is_docker_installed',
483+
return_value=False,
484+
)
485+
def test_local_run_no_docker(self, mock_docker):
486+
"""Test local-run fails gracefully when Docker is not installed."""
487+
runner = CliRunner()
488+
489+
with runner.isolated_filesystem():
490+
# Create minimal pipeline step structure
491+
os.makedirs('1', exist_ok=True)
492+
with open('config.yaml', 'w') as f:
493+
f.write(
494+
'pipeline_step:\n id: test\n user_id: u\n app_id: a\n'
495+
'build_info:\n python_version: "3.12"\n'
496+
'pipeline_step_compute_info:\n cpu_limit: "500m"\n cpu_memory: "500Mi"\n num_accelerators: 0\n'
497+
)
498+
with open('requirements.txt', 'w') as f:
499+
f.write('clarifai\n')
500+
with open('1/pipeline_step.py', 'w') as f:
501+
f.write('print("hello")\n')
502+
503+
result = runner.invoke(local_run, ['.'])
504+
505+
assert result.exit_code != 0
506+
assert 'Docker is not installed' in result.output

tests/runners/test_pipeline_run_locally_container.py renamed to tests/runners/test_pipeline_step_run_locally_container.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def dummy_pipeline_step_path(tmp_path):
2121

2222

2323
@pytest.fixture
24-
def pipeline_run_locally(dummy_pipeline_step_path):
25-
"""Instantiate PipelineRunLocally with the dummy pipeline step."""
24+
def pipeline_step_run_locally(dummy_pipeline_step_path):
25+
"""Instantiate PipelineStepRunLocally with the dummy pipeline step."""
2626
return PipelineStepRunLocally(dummy_pipeline_step_path)
2727

2828

@@ -31,28 +31,28 @@ def pipeline_run_locally(dummy_pipeline_step_path):
3131
sys.platform not in ["linux", "darwin"],
3232
reason="Test only runs on Linux and macOS.",
3333
)
34-
def test_pipeline_step_docker_build_and_run(pipeline_run_locally):
34+
def test_pipeline_step_docker_build_and_run(pipeline_step_run_locally):
3535
"""Test building a Docker image and running a pipeline step in a container."""
36-
assert pipeline_run_locally.is_docker_installed(), "Docker not installed."
36+
assert pipeline_step_run_locally.is_docker_installed(), "Docker not installed."
3737

38-
pipeline_run_locally.builder.create_dockerfile()
39-
image_tag = pipeline_run_locally._docker_hash()
40-
step_id = pipeline_run_locally.config['pipeline_step']['id'].lower()
38+
pipeline_step_run_locally.builder.create_dockerfile()
39+
image_tag = pipeline_step_run_locally._docker_hash()
40+
step_id = pipeline_step_run_locally.config['pipeline_step']['id'].lower()
4141
image_name = f"{step_id}:{image_tag}"
4242
container_name = "test-pipeline-step-container"
4343

44-
if not pipeline_run_locally.docker_image_exists(image_name):
45-
pipeline_run_locally.build_docker_image(image_name=image_name)
44+
if not pipeline_step_run_locally.docker_image_exists(image_name):
45+
pipeline_step_run_locally.build_docker_image(image_name=image_name)
4646

4747
try:
48-
pipeline_run_locally.run_pipeline_step_container(
48+
pipeline_step_run_locally.run_pipeline_step_container(
4949
image_name=image_name,
5050
container_name=container_name,
5151
)
5252
except subprocess.CalledProcessError:
5353
pytest.fail("Failed to run pipeline step inside the docker container.")
5454
finally:
55-
if pipeline_run_locally.container_exists(container_name):
56-
pipeline_run_locally.stop_docker_container(container_name)
57-
pipeline_run_locally.remove_docker_container(container_name)
58-
pipeline_run_locally.remove_docker_image(image_name)
55+
if pipeline_step_run_locally.container_exists(container_name):
56+
pipeline_step_run_locally.stop_docker_container(container_name)
57+
pipeline_step_run_locally.remove_docker_container(container_name)
58+
pipeline_step_run_locally.remove_docker_image(image_name)

0 commit comments

Comments
 (0)