diff --git a/lib/cli/src/crewai_cli/utils.py b/lib/cli/src/crewai_cli/utils.py index 68b0232b1..50b36d486 100644 --- a/lib/cli/src/crewai_cli/utils.py +++ b/lib/cli/src/crewai_cli/utils.py @@ -1,23 +1,26 @@ from __future__ import annotations -from functools import reduce import os from pathlib import Path import shutil -import sys from typing import Any, cast import click +from crewai_core.project import ( + _get_nested_value as _get_nested_value, + _get_project_attribute as _get_project_attribute, + get_project_description as get_project_description, + get_project_name as get_project_name, + get_project_version as get_project_version, + parse_toml as parse_toml, + read_toml as read_toml, +) from rich.console import Console -import tomli from crewai_cli.config import Settings from crewai_cli.constants import ENV_VARS -if sys.version_info >= (3, 11): - import tomllib - console = Console() @@ -38,91 +41,6 @@ def copy_template( click.secho(f" - Created {dst}", fg="green") -def read_toml(file_path: str = "pyproject.toml") -> dict[str, Any]: - """Read the content of a TOML file and return it as a dictionary.""" - with open(file_path, "rb") as f: - return tomli.load(f) - - -def parse_toml(content: str) -> dict[str, Any]: - if sys.version_info >= (3, 11): - return tomllib.loads(content) - return tomli.loads(content) - - -def get_project_name( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project name from the pyproject.toml file.""" - return _get_project_attribute(pyproject_path, ["project", "name"], require=require) - - -def get_project_version( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project version from the pyproject.toml file.""" - return _get_project_attribute( - pyproject_path, ["project", "version"], require=require - ) - - -def get_project_description( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project description from the pyproject.toml file.""" - return _get_project_attribute( - pyproject_path, ["project", "description"], require=require - ) - - -def _get_project_attribute( - pyproject_path: str, keys: list[str], require: bool -) -> Any | None: - """Get an attribute from the pyproject.toml file.""" - attribute = None - - try: - with open(pyproject_path, "r") as f: - pyproject_content = parse_toml(f.read()) - - dependencies = ( - _get_nested_value(pyproject_content, ["project", "dependencies"]) or [] - ) - if not any(True for dep in dependencies if "crewai" in dep): - raise Exception("crewai is not in the dependencies.") - - attribute = _get_nested_value(pyproject_content, keys) - except FileNotFoundError: - console.print(f"Error: {pyproject_path} not found.", style="bold red") - except KeyError: - console.print( - f"Error: {pyproject_path} is not a valid pyproject.toml file.", - style="bold red", - ) - except Exception as e: - if sys.version_info >= (3, 11) and isinstance(e, tomllib.TOMLDecodeError): - console.print( - f"Error: {pyproject_path} is not a valid TOML file.", style="bold red" - ) - else: - console.print( - f"Error reading the pyproject.toml file: {e}", style="bold red" - ) - - if require and not attribute: - console.print( - f"Unable to read '{'.'.join(keys)}' in the pyproject.toml file. Please verify that the file exists and contains the specified attribute.", - style="bold red", - ) - raise SystemExit - - return attribute - - -def _get_nested_value(data: dict[str, Any], keys: list[str]) -> Any: - return reduce(dict.__getitem__, keys, data) - - def fetch_and_json_env_file(env_file_path: str = ".env") -> dict[str, Any]: """Fetch the environment variables from a .env file and return them as a dictionary.""" try: diff --git a/lib/crewai-core/src/crewai_core/project.py b/lib/crewai-core/src/crewai_core/project.py new file mode 100644 index 000000000..29d322304 --- /dev/null +++ b/lib/crewai-core/src/crewai_core/project.py @@ -0,0 +1,109 @@ +"""TOML / pyproject.toml utilities shared by crewai and crewai-cli.""" + +from __future__ import annotations + +from functools import reduce +import sys +from typing import Any + +from rich.console import Console +import tomli + + +if sys.version_info >= (3, 11): + import tomllib + +console = Console() + + +def read_toml(file_path: str = "pyproject.toml") -> dict[str, Any]: + """Read a TOML file from disk and return its parsed contents.""" + with open(file_path, "rb") as f: + return tomli.load(f) + + +def parse_toml(content: str) -> dict[str, Any]: + """Parse a TOML string and return its parsed contents.""" + if sys.version_info >= (3, 11): + return tomllib.loads(content) + return tomli.loads(content) + + +def _get_nested_value(data: dict[str, Any], keys: list[str]) -> Any: + return reduce(dict.__getitem__, keys, data) + + +def _get_project_attribute( + pyproject_path: str, keys: list[str], require: bool +) -> Any | None: + """Look up a dotted attribute path inside ``pyproject_path``. + + The file must declare ``crewai`` in ``[project].dependencies`` for the + lookup to succeed (a guard against running these helpers outside a crewai + project directory). When ``require=True``, missing attributes raise + ``SystemExit`` after printing a friendly error. + """ + attribute = None + + try: + with open(pyproject_path, "r") as f: + pyproject_content = parse_toml(f.read()) + + dependencies = ( + _get_nested_value(pyproject_content, ["project", "dependencies"]) or [] + ) + if not any(True for dep in dependencies if "crewai" in dep): + raise Exception("crewai is not in the dependencies.") + + attribute = _get_nested_value(pyproject_content, keys) + except FileNotFoundError: + console.print(f"Error: {pyproject_path} not found.", style="bold red") + except KeyError: + console.print( + f"Error: {pyproject_path} is not a valid pyproject.toml file.", + style="bold red", + ) + except Exception as e: + if sys.version_info >= (3, 11) and isinstance(e, tomllib.TOMLDecodeError): + console.print( + f"Error: {pyproject_path} is not a valid TOML file.", style="bold red" + ) + else: + console.print( + f"Error reading the pyproject.toml file: {e}", style="bold red" + ) + + if require and not attribute: + console.print( + f"Unable to read '{'.'.join(keys)}' in the pyproject.toml file. " + "Please verify that the file exists and contains the specified attribute.", + style="bold red", + ) + raise SystemExit + + return attribute + + +def get_project_name( + pyproject_path: str = "pyproject.toml", require: bool = False +) -> str | None: + """Return the project name from ``pyproject.toml``.""" + return _get_project_attribute(pyproject_path, ["project", "name"], require=require) + + +def get_project_version( + pyproject_path: str = "pyproject.toml", require: bool = False +) -> str | None: + """Return the project version from ``pyproject.toml``.""" + return _get_project_attribute( + pyproject_path, ["project", "version"], require=require + ) + + +def get_project_description( + pyproject_path: str = "pyproject.toml", require: bool = False +) -> str | None: + """Return the project description from ``pyproject.toml``.""" + return _get_project_attribute( + pyproject_path, ["project", "description"], require=require + ) diff --git a/lib/crewai/src/crewai/utilities/project_utils.py b/lib/crewai/src/crewai/utilities/project_utils.py index 1f5635fc4..7bcb9b4e0 100644 --- a/lib/crewai/src/crewai/utilities/project_utils.py +++ b/lib/crewai/src/crewai/utilities/project_utils.py @@ -2,7 +2,7 @@ from collections.abc import Generator, Mapping from contextlib import contextmanager -from functools import lru_cache, reduce +from functools import lru_cache import hashlib import importlib.util import inspect @@ -13,106 +13,25 @@ import sys import types from typing import Any, cast, get_type_hints +from crewai_core.project import ( + _get_nested_value as _get_nested_value, + _get_project_attribute as _get_project_attribute, + get_project_description as get_project_description, + get_project_name as get_project_name, + get_project_version as get_project_version, + parse_toml as parse_toml, + read_toml as read_toml, +) from rich.console import Console -import tomli from crewai.crew import Crew from crewai.flow import Flow from crewai.settings import Settings -if sys.version_info >= (3, 11): - import tomllib - console = Console() -def read_toml(file_path: str = "pyproject.toml") -> dict[str, Any]: - """Read the content of a TOML file and return it as a dictionary.""" - with open(file_path, "rb") as f: - return tomli.load(f) - - -def parse_toml(content: str) -> dict[str, Any]: - """Parse a TOML string and return it as a dictionary.""" - if sys.version_info >= (3, 11): - return tomllib.loads(content) - return tomli.loads(content) - - -def get_project_name( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project name from the pyproject.toml file.""" - return _get_project_attribute(pyproject_path, ["project", "name"], require=require) - - -def get_project_version( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project version from the pyproject.toml file.""" - return _get_project_attribute( - pyproject_path, ["project", "version"], require=require - ) - - -def get_project_description( - pyproject_path: str = "pyproject.toml", require: bool = False -) -> str | None: - """Get the project description from the pyproject.toml file.""" - return _get_project_attribute( - pyproject_path, ["project", "description"], require=require - ) - - -def _get_project_attribute( - pyproject_path: str, keys: list[str], require: bool -) -> Any | None: - """Get an attribute from the pyproject.toml file.""" - attribute = None - - try: - with open(pyproject_path, "r") as f: - pyproject_content = parse_toml(f.read()) - - dependencies = ( - _get_nested_value(pyproject_content, ["project", "dependencies"]) or [] - ) - if not any(True for dep in dependencies if "crewai" in dep): - raise Exception("crewai is not in the dependencies.") - - attribute = _get_nested_value(pyproject_content, keys) - except FileNotFoundError: - console.print(f"Error: {pyproject_path} not found.", style="bold red") - except KeyError: - console.print( - f"Error: {pyproject_path} is not a valid pyproject.toml file.", - style="bold red", - ) - except Exception as e: - if sys.version_info >= (3, 11) and isinstance(e, tomllib.TOMLDecodeError): - console.print( - f"Error: {pyproject_path} is not a valid TOML file.", style="bold red" - ) - else: - console.print( - f"Error reading the pyproject.toml file: {e}", style="bold red" - ) - - if require and not attribute: - console.print( - f"Unable to read '{'.'.join(keys)}' in the pyproject.toml file. Please verify that the file exists and contains the specified attribute.", - style="bold red", - ) - raise SystemExit - - return attribute - - -def _get_nested_value(data: dict[str, Any], keys: list[str]) -> Any: - return reduce(dict.__getitem__, keys, data) - - def get_crews(crew_path: str = "crew.py", require: bool = False) -> list[Crew]: """Get the crew instances from a file.""" crew_instances = []