From 7ff4cd21318e5b498ae43808d262739dea55668e Mon Sep 17 00:00:00 2001 From: Eduardo Chiarotti Date: Thu, 10 Oct 2024 21:07:57 -0300 Subject: [PATCH] feat: add crewai update to migrate from poetry to uv --- pyproject.toml | 1 + src/crewai/cli/cli.py | 7 +++ src/crewai/cli/run_crew.py | 6 ++- src/crewai/cli/update_crew.py | 87 +++++++++++++++++++++++++++++++++++ uv.lock | 11 +++++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/crewai/cli/update_crew.py diff --git a/pyproject.toml b/pyproject.toml index 13325d03b..0540c2937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "litellm>=1.44.22", "pyvis>=0.3.2", "uv>=0.4.18", + "tomli-w>=1.1.0", ] [project.urls] diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 243502b43..55c8e5b43 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -21,6 +21,7 @@ from .run_crew import run_crew from .run_flow import run_flow from .tools.main import ToolCommand from .train_crew import train_crew +from .update_crew import update_crew @click.group() @@ -188,6 +189,12 @@ def run(): run_crew() +@crewai.command() +def update(): + """Update the pyproject.toml of the Crew project to use uv.""" + update_crew() + + @crewai.command() def signup(): """Sign Up/Login to CrewAI+.""" diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 7eb24c123..2d246cb0d 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -17,7 +17,11 @@ def run_crew() -> None: except subprocess.CalledProcessError as e: click.echo(f"An error occurred while running the crew: {e}", err=True) - click.echo(e.output, err=True) + click.echo(e.output, err=True, nl=True) + click.secho( + "It's possible that you are using an old version of crewAI that uses poetry, please run `crewai update` to update your pyproject.toml to use uv.", + fg="yellow", + ) except Exception as e: click.echo(f"An unexpected error occurred: {e}", err=True) diff --git a/src/crewai/cli/update_crew.py b/src/crewai/cli/update_crew.py new file mode 100644 index 000000000..66f9ab6bd --- /dev/null +++ b/src/crewai/cli/update_crew.py @@ -0,0 +1,87 @@ +import shutil + +import tomli_w +import tomllib + + +def update_crew() -> None: + """Update the pyproject.toml of the Crew project to use uv.""" + migrate_pyproject("pyproject.toml", "pyproject.toml") + + +def migrate_pyproject(input_file, output_file): + """ + Migrate the pyproject.toml to the new format. + + This function is used to migrate the pyproject.toml to the new format. + And it will be used to migrate the pyproject.toml to the new format when uv is used. + When the time comes that uv supports the new format, this function will be deprecated. + """ + + # Read the input pyproject.toml + with open(input_file, "rb") as f: + pyproject = tomllib.load(f) + + # Initialize the new project structure + new_pyproject = { + "project": {}, + "build-system": {"requires": ["hatchling"], "build-backend": "hatchling.build"}, + } + + # Migrate project metadata + if "tool" in pyproject and "poetry" in pyproject["tool"]: + poetry = pyproject["tool"]["poetry"] + new_pyproject["project"]["name"] = poetry.get("name") + new_pyproject["project"]["version"] = poetry.get("version") + new_pyproject["project"]["description"] = poetry.get("description") + new_pyproject["project"]["authors"] = [ + { + "name": author.split("<")[0].strip(), + "email": author.split("<")[1].strip(">").strip(), + } + for author in poetry.get("authors", []) + ] + new_pyproject["project"]["requires-python"] = poetry.get("python") + else: + # If it's already in the new format, just copy the project section + new_pyproject["project"] = pyproject.get("project", {}) + + # Migrate or copy dependencies + if "dependencies" in new_pyproject["project"]: + # If dependencies are already in the new format, keep them as is + pass + elif "dependencies" in poetry: + new_pyproject["project"]["dependencies"] = [] + for dep, version in poetry["dependencies"].items(): + if isinstance(version, dict): # Handle extras + extras = ",".join(version.get("extras", [])) + new_dep = f"{dep}[{extras}]" + if "version" in version: + new_dep += f"{version['version']}" + elif dep == "python": + new_pyproject["project"]["requires-python"] = version + continue + else: + new_dep = f"{dep}{version}" + new_pyproject["project"]["dependencies"].append(new_dep) + + # Migrate or copy scripts + if "scripts" in poetry: + new_pyproject["project"]["scripts"] = poetry["scripts"] + elif "scripts" in pyproject.get("project", {}): + new_pyproject["project"]["scripts"] = pyproject["project"]["scripts"] + + # Migrate optional dependencies + if "extras" in poetry: + new_pyproject["project"]["optional-dependencies"] = poetry["extras"] + + # Backup the old pyproject.toml + backup_file = "pyproject-old.toml" + shutil.copy2(input_file, backup_file) + print(f"Original pyproject.toml backed up as {backup_file}") + + # Write the new pyproject.toml + with open(output_file, "wb") as f: + tomli_w.dump(new_pyproject, f) + + print(f"Migration complete. New pyproject.toml written to {output_file}") diff --git a/uv.lock b/uv.lock index e1dfa5eac..6abd2b4a8 100644 --- a/uv.lock +++ b/uv.lock @@ -649,6 +649,7 @@ dependencies = [ { name = "python-dotenv" }, { name = "pyvis" }, { name = "regex" }, + { name = "tomli-w" }, { name = "uv" }, ] @@ -703,6 +704,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "pyvis", specifier = ">=0.3.2" }, { name = "regex", specifier = ">=2024.9.11" }, + { name = "tomli-w", specifier = ">=1.1.0" }, { name = "uv", specifier = ">=0.4.18" }, ] @@ -4390,6 +4392,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 }, ] +[[package]] +name = "tomli-w" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/19/b65f1a088ee23e37cdea415b357843eca8b1422a7b11a9eee6e35d4ec273/tomli_w-1.1.0.tar.gz", hash = "sha256:49e847a3a304d516a169a601184932ef0f6b61623fe680f836a2aa7128ed0d33", size = 6929 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ac/ce90573ba446a9bbe65838ded066a805234d159b4446ae9f8ec5bbd36cbd/tomli_w-1.1.0-py3-none-any.whl", hash = "sha256:1403179c78193e3184bfaade390ddbd071cba48a32a2e62ba11aae47490c63f7", size = 6440 }, +] + [[package]] name = "tqdm" version = "4.66.5"