Last active
June 19, 2025 01:08
-
-
Save hiranp/8bb28e882d6884338ea50fa17d923764 to your computer and use it in GitHub Desktop.
Revisions
-
hiranp revised this gist
Jun 19, 2025 . No changes.There are no files selected for viewing
-
hiranp revised this gist
Jun 19, 2025 . 1 changed file with 172 additions and 38 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,30 @@ #!/usr/bin/env python3 """ Script to convert conda environment.yml or requirements.txt to uv-compatible pyproject.toml format This script reads a conda environment file (environment.yml or requirements.txt with conda format) and converts all dependencies to a format compatible with uv in pyproject.toml. Features: - Extracts conda and pip dependencies from environment files - Preserves version information from the source file - Converts version constraints to '>=' format for better compatibility with uv - Filters out internal conda packages (starting with _) - Adds dependencies to pyproject.toml under [project.dependencies] REQUIRES: - pyyaml - toml INSTALLATION: pip install pyyaml toml EXECUTION: python convert_requirements.py [requirements.txt | environment.yml] EXAMPLE: python convert_requirements.py requirements.txt python convert_requirements.py environment.yml """ import yaml @@ -13,7 +37,7 @@ def parse_requirements_txt(filename): """ Parse a requirements.txt or environment.yml file to extract dependencies """ conda_dependencies = [] pip_dependencies = [] with open(filename, "r") as file: @@ -29,17 +53,16 @@ def parse_requirements_txt(filename): # Handle pip dependencies inside conda env file for pip_dep in dep["pip"]: pip_dependencies.append(pip_dep) elif isinstance(dep, str) and not dep.startswith("python="): # Keep conda dependencies with their versions, but skip python version if "=" in dep: package_parts = dep.split("=") if not package_parts[0].startswith( "_" ): # Skip internal packages conda_dependencies.append(dep) else: conda_dependencies.append(dep) except yaml.YAMLError as e: print(f"Error parsing YAML: {e}") return [], [] @@ -71,11 +94,16 @@ def parse_requirements_txt(filename): if in_deps_section and line.startswith("-"): dep = line[1:].strip() if not dep.startswith("python="): # Skip Python version if "=" in dep: package_parts = dep.split("=") if not package_parts[0].startswith( "_" ): # Skip internal packages conda_dependencies.append(dep) else: if not dep.startswith("_"): # Skip internal packages conda_dependencies.append(dep) if in_pip_section and line.startswith("-"): pip_dep = line[1:].strip() @@ -87,17 +115,27 @@ def parse_requirements_txt(filename): if line and not line.startswith("#"): pip_dependencies.append(line) # Format conda dependencies to PyPI format formatted_conda_deps = [] for dep in conda_dependencies: if "=" in dep: parts = dep.split("=") name = parts[0] if len(parts) >= 2: version = parts[1] # Convert conda version constraint to PyPI format formatted_conda_deps.append(f"{name}=={version}") else: formatted_conda_deps.append(name) else: formatted_conda_deps.append(dep) # Keep pip dependencies with their exact version constraints formatted_pip_deps = [] for dep in pip_dependencies: formatted_pip_deps.append(dep) return formatted_conda_deps, formatted_pip_deps def update_pyproject_toml(pyproject_path, conda_deps, pip_deps): @@ -123,17 +161,33 @@ def update_pyproject_toml(pyproject_path, conda_deps, pip_deps): if "dependencies" not in pyproject_data["project"]: pyproject_data["project"]["dependencies"] = [] # Combine and format dependencies for pyproject.toml all_deps = [] # Process conda dependencies for dep in conda_deps: if dep: # Format dependency for pyproject.toml using >= instead of == for better compatibility if "==" in dep: name, version = dep.split("==", 1) all_deps.append(f"{name}>={version}") elif "=" in dep: # Convert conda version format to PyPI format with >= name, version = dep.split("=", 1) all_deps.append(f"{name}>={version}") else: all_deps.append(dep) # Process pip dependencies - convert == to >= for better compatibility for dep in pip_deps: if dep: if "==" in dep: name, version = dep.split("==", 1) all_deps.append(f"{name}>={version}") else: all_deps.append(dep) # Update dependencies - We'll handle writing them in a custom way pyproject_data["project"]["dependencies"] = all_deps # Add tool.uv section @@ -143,17 +197,78 @@ def update_pyproject_toml(pyproject_path, conda_deps, pip_deps): if "uv" not in pyproject_data["tool"]: pyproject_data["tool"]["uv"] = {} # Write updated pyproject.toml with proper multi-line formatting for dependencies with open(pyproject_path, "w") as file: # Get all non-dependencies sections first temp_data = pyproject_data.copy() dependencies = temp_data["project"].pop("dependencies") # Write the non-dependencies parts for section, content in temp_data.items(): if section == "project": file.write(f"[{section}]\n") for key, value in content.items(): if isinstance(value, str): file.write(f'{key} = "{value}"\n') else: file.write(f"{key} = {value}\n") # Write dependencies with proper formatting file.write("dependencies = [\n") for dep in sorted(dependencies): file.write(f' "{dep}",\n') file.write("]\n\n") else: file.write(f"[{section}]\n") for key, value in content.items(): file.write(f"{key} = {value}\n") file.write("\n") print(f"Updated {pyproject_path} with {len(all_deps)} dependencies") def generate_requirements_txt(deps, output_file): """ Generate a requirements.txt file from a list of dependencies """ # Sort dependencies alphabetically for better readability sorted_deps = sorted(deps) with open(output_file, "w") as f: for dep in sorted_deps: f.write(f"{dep}\n") print(f"Generated {output_file} with {len(deps)} dependencies") def main(): # Parse command line arguments import argparse parser = argparse.ArgumentParser( description="Convert conda environment to uv-compatible pyproject.toml" ) parser.add_argument( "input_file", nargs="?", help="Input environment.yml or requirements.txt file (default: auto-detect)", ) parser.add_argument( "-o", "--output", default="pyproject.toml", help="Output pyproject.toml file (default: pyproject.toml)", ) parser.add_argument( "--requirements", action="store_true", help="Also generate a requirements.txt file", ) args = parser.parse_args() # Determine input file requirements_file = args.input_file if not requirements_file: # Default to looking for requirements.txt or environment.yml if os.path.exists("requirements.txt"): requirements_file = "requirements.txt" @@ -163,7 +278,7 @@ def main(): print("Error: Could not find requirements.txt or environment.yml") return 1 pyproject_path = args.output print(f"Converting {requirements_file} to {pyproject_path}") @@ -178,8 +293,27 @@ def main(): for dep in pip_deps: print(f" - {dep}") # Update pyproject.toml update_pyproject_toml(pyproject_path, conda_deps, pip_deps) # Generate requirements.txt if requested if args.requirements: # Combine all dependencies all_deps = [] for dep in conda_deps + pip_deps: if "==" in dep: # Convert to >= for requirements-uv.txt to match pyproject.toml name, version = dep.split("==", 1) all_deps.append(f"{name}>={version}") elif ">=" in dep: # Keep the >= format all_deps.append(dep) else: all_deps.append(dep) # Generate requirements.txt generate_requirements_txt(all_deps, "requirements-uv.txt") print( f"\nFound {len(conda_deps)} conda dependencies and {len(pip_deps)} pip dependencies" ) -
hiranp created this gist
Jun 19, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,191 @@ #!/usr/bin/env python3 """ Script to convert conda environment.yml or requirements.txt to uv-compatible pyproject.toml format """ import yaml import toml import os import sys def parse_requirements_txt(filename): """ Parse a requirements.txt or environment.yml file to extract dependencies """ dependencies = [] pip_dependencies = [] with open(filename, "r") as file: content = file.read() if filename.endswith(".yml") or filename.endswith(".yaml"): # Parse YAML content for environment.yml try: env_yaml = yaml.safe_load(content) if "dependencies" in env_yaml: for dep in env_yaml["dependencies"]: if isinstance(dep, dict) and "pip" in dep: # Handle pip dependencies inside conda env file for pip_dep in dep["pip"]: pip_dependencies.append(pip_dep) elif ( isinstance(dep, str) and not dep.startswith("_") and not dep.startswith("python=") ): # Only add non-internal conda packages (not starting with _) # and skip python version specification package = dep.split("=")[ 0 ] # Extract package name without version dependencies.append(package) except yaml.YAMLError as e: print(f"Error parsing YAML: {e}") return [], [] else: # Assume requirements.txt format lines = content.split("\n") # Check if it contains conda-style entries if any( line.startswith("name:") or line.startswith("channels:") for line in lines ): # This is a conda env file saved as .txt in_pip_section = False in_deps_section = False for line in lines: line = line.strip() if line.startswith("dependencies:"): in_deps_section = True in_pip_section = False continue if in_deps_section and line.startswith("- pip:"): in_pip_section = True in_deps_section = False continue if in_deps_section and line.startswith("-"): dep = line[1:].strip() if not dep.startswith("_") and not dep.startswith("python="): package = dep.split("=")[ 0 ] # Extract package name without version dependencies.append(package) if in_pip_section and line.startswith("-"): pip_dep = line[1:].strip() pip_dependencies.append(pip_dep) else: # Regular pip requirements.txt for line in lines: line = line.strip() if line and not line.startswith("#"): pip_dependencies.append(line) # Clean up version constraints for pip dependencies cleaned_pip_deps = [] for dep in pip_dependencies: # Convert == to >= if "==" in dep: name, version = dep.split("==", 1) cleaned_pip_deps.append(f"{name}>={version}") else: cleaned_pip_deps.append(dep) return dependencies, cleaned_pip_deps def update_pyproject_toml(pyproject_path, conda_deps, pip_deps): """ Update or create a pyproject.toml file with the extracted dependencies """ pyproject_data = {} # Load existing pyproject.toml if it exists if os.path.exists(pyproject_path): with open(pyproject_path, "r") as file: try: pyproject_data = toml.load(file) except toml.TomlDecodeError as e: print(f"Error parsing existing pyproject.toml: {e}") pyproject_data = {} # Initialize project section if it doesn't exist if "project" not in pyproject_data: pyproject_data["project"] = {} # Update dependencies list if "dependencies" not in pyproject_data["project"]: pyproject_data["project"]["dependencies"] = [] # Combine conda and pip dependencies all_deps = [] for dep in conda_deps: if dep and dep not in all_deps: all_deps.append(dep) for dep in pip_deps: if dep and dep not in all_deps: all_deps.append(dep) # Update dependencies pyproject_data["project"]["dependencies"] = all_deps # Add tool.uv section if "tool" not in pyproject_data: pyproject_data["tool"] = {} if "uv" not in pyproject_data["tool"]: pyproject_data["tool"]["uv"] = {} # Write updated pyproject.toml with open(pyproject_path, "w") as file: toml.dump(pyproject_data, file) print(f"Updated {pyproject_path} with {len(all_deps)} dependencies") def main(): if len(sys.argv) > 1: requirements_file = sys.argv[1] else: # Default to looking for requirements.txt or environment.yml if os.path.exists("requirements.txt"): requirements_file = "requirements.txt" elif os.path.exists("environment.yml"): requirements_file = "environment.yml" else: print("Error: Could not find requirements.txt or environment.yml") return 1 pyproject_path = "pyproject.toml" print(f"Converting {requirements_file} to {pyproject_path}") conda_deps, pip_deps = parse_requirements_txt(requirements_file) # Print found dependencies for debugging print("Conda dependencies:") for dep in conda_deps: print(f" - {dep}") print("\nPip dependencies:") for dep in pip_deps: print(f" - {dep}") update_pyproject_toml(pyproject_path, conda_deps, pip_deps) print( f"\nFound {len(conda_deps)} conda dependencies and {len(pip_deps)} pip dependencies" ) print(f"Updated {pyproject_path} successfully!") return 0 if __name__ == "__main__": sys.exit(main())